1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsWrapperCache.h"
8 
9 #include "mozilla/dom/Element.h"
10 #include "mozilla/dom/ElementBinding.h"
11 #include "mozilla/dom/Promise.h"
12 #include "mozilla/dom/UserActivation.h"
13 #include "mozilla/dom/VRDisplay.h"
14 #include "mozilla/dom/VRDisplayBinding.h"
15 #include "mozilla/HoldDropJSObjects.h"
16 #include "mozilla/Base64.h"
17 #include "mozilla/StaticPrefs_dom.h"
18 #include "mozilla/gfx/DataSurfaceHelpers.h"
19 #include "Navigator.h"
20 #include "gfxUtils.h"
21 #include "gfxVR.h"
22 #include "VRDisplayClient.h"
23 #include "VRManagerChild.h"
24 #include "VRDisplayPresentation.h"
25 #include "nsIObserverService.h"
26 #include "nsIFrame.h"
27 #include "nsISupportsPrimitives.h"
28 
29 using namespace mozilla::gfx;
30 
31 namespace mozilla {
32 namespace dom {
33 
VRFieldOfView(nsISupports * aParent,double aUpDegrees,double aRightDegrees,double aDownDegrees,double aLeftDegrees)34 VRFieldOfView::VRFieldOfView(nsISupports* aParent, double aUpDegrees,
35                              double aRightDegrees, double aDownDegrees,
36                              double aLeftDegrees)
37     : mParent(aParent),
38       mUpDegrees(aUpDegrees),
39       mRightDegrees(aRightDegrees),
40       mDownDegrees(aDownDegrees),
41       mLeftDegrees(aLeftDegrees) {}
42 
VRFieldOfView(nsISupports * aParent,const gfx::VRFieldOfView & aSrc)43 VRFieldOfView::VRFieldOfView(nsISupports* aParent,
44                              const gfx::VRFieldOfView& aSrc)
45     : mParent(aParent),
46       mUpDegrees(aSrc.upDegrees),
47       mRightDegrees(aSrc.rightDegrees),
48       mDownDegrees(aSrc.downDegrees),
49       mLeftDegrees(aSrc.leftDegrees) {}
50 
HasPosition() const51 bool VRDisplayCapabilities::HasPosition() const {
52   return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Position) ||
53          bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated);
54 }
55 
HasOrientation() const56 bool VRDisplayCapabilities::HasOrientation() const {
57   return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Orientation);
58 }
59 
HasExternalDisplay() const60 bool VRDisplayCapabilities::HasExternalDisplay() const {
61   return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_External);
62 }
63 
CanPresent() const64 bool VRDisplayCapabilities::CanPresent() const {
65   return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Present);
66 }
67 
MaxLayers() const68 uint32_t VRDisplayCapabilities::MaxLayers() const {
69   return CanPresent() ? 1 : 0;
70 }
71 
UpdateDisplayClient(already_AddRefed<gfx::VRDisplayClient> aClient)72 void VRDisplay::UpdateDisplayClient(
73     already_AddRefed<gfx::VRDisplayClient> aClient) {
74   mClient = std::move(aClient);
75 }
76 
77 /*static*/
RefreshVRDisplays(uint64_t aWindowId)78 bool VRDisplay::RefreshVRDisplays(uint64_t aWindowId) {
79   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
80   return vm && vm->RefreshVRDisplaysWithCallback(aWindowId);
81 }
82 
83 /*static*/
UpdateVRDisplays(nsTArray<RefPtr<VRDisplay>> & aDisplays,nsPIDOMWindowInner * aWindow)84 void VRDisplay::UpdateVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays,
85                                  nsPIDOMWindowInner* aWindow) {
86   nsTArray<RefPtr<VRDisplay>> displays;
87 
88   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
89   nsTArray<RefPtr<gfx::VRDisplayClient>> updatedDisplays;
90   if (vm) {
91     vm->GetVRDisplays(updatedDisplays);
92     for (size_t i = 0; i < updatedDisplays.Length(); i++) {
93       RefPtr<gfx::VRDisplayClient> display = updatedDisplays[i];
94       bool isNewDisplay = true;
95       for (size_t j = 0; j < aDisplays.Length(); j++) {
96         if (aDisplays[j]->GetClient()->GetDisplayInfo().GetDisplayID() ==
97             display->GetDisplayInfo().GetDisplayID()) {
98           displays.AppendElement(aDisplays[j]);
99           isNewDisplay = false;
100         } else {
101           RefPtr<gfx::VRDisplayClient> ref = display;
102           aDisplays[j]->UpdateDisplayClient(do_AddRef(display));
103           displays.AppendElement(aDisplays[j]);
104           isNewDisplay = false;
105         }
106       }
107 
108       if (isNewDisplay) {
109         displays.AppendElement(new VRDisplay(aWindow, display));
110       }
111     }
112   }
113 
114   aDisplays = std::move(displays);
115 }
116 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRFieldOfView,mParent)117 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRFieldOfView, mParent)
118 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFieldOfView, AddRef)
119 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFieldOfView, Release)
120 
121 JSObject* VRFieldOfView::WrapObject(JSContext* aCx,
122                                     JS::Handle<JSObject*> aGivenProto) {
123   return VRFieldOfView_Binding::Wrap(aCx, this, aGivenProto);
124 }
125 
126 NS_IMPL_CYCLE_COLLECTION_CLASS(VREyeParameters)
127 
128 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VREyeParameters)
129   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mFOV)
130   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
131   tmp->mOffset = nullptr;
132 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
133 
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VREyeParameters)134 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VREyeParameters)
135   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mFOV)
136 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
137 
138 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VREyeParameters)
139   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
140   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOffset)
141 NS_IMPL_CYCLE_COLLECTION_TRACE_END
142 
143 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VREyeParameters, AddRef)
144 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VREyeParameters, Release)
145 
146 VREyeParameters::VREyeParameters(nsISupports* aParent,
147                                  const gfx::Point3D& aEyeTranslation,
148                                  const gfx::VRFieldOfView& aFOV,
149                                  const gfx::IntSize& aRenderSize)
150     : mParent(aParent),
151       mEyeTranslation(aEyeTranslation),
152       mRenderSize(aRenderSize) {
153   mFOV = new VRFieldOfView(aParent, aFOV);
154   mozilla::HoldJSObjects(this);
155 }
156 
~VREyeParameters()157 VREyeParameters::~VREyeParameters() { mozilla::DropJSObjects(this); }
158 
FieldOfView()159 VRFieldOfView* VREyeParameters::FieldOfView() { return mFOV; }
160 
GetOffset(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)161 void VREyeParameters::GetOffset(JSContext* aCx,
162                                 JS::MutableHandle<JSObject*> aRetval,
163                                 ErrorResult& aRv) {
164   if (!mOffset) {
165     // Lazily create the Float32Array
166     mOffset =
167         dom::Float32Array::Create(aCx, this, 3, mEyeTranslation.components);
168     if (!mOffset) {
169       aRv.NoteJSContextException(aCx);
170       return;
171     }
172   }
173   aRetval.set(mOffset);
174 }
175 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)176 JSObject* VREyeParameters::WrapObject(JSContext* aCx,
177                                       JS::Handle<JSObject*> aGivenProto) {
178   return VREyeParameters_Binding::Wrap(aCx, this, aGivenProto);
179 }
180 
VRStageParameters(nsISupports * aParent,const gfx::Matrix4x4 & aSittingToStandingTransform,const gfx::Size & aSize)181 VRStageParameters::VRStageParameters(
182     nsISupports* aParent, const gfx::Matrix4x4& aSittingToStandingTransform,
183     const gfx::Size& aSize)
184     : mParent(aParent),
185       mSittingToStandingTransform(aSittingToStandingTransform),
186       mSittingToStandingTransformArray(nullptr),
187       mSize(aSize) {
188   mozilla::HoldJSObjects(this);
189 }
190 
~VRStageParameters()191 VRStageParameters::~VRStageParameters() { mozilla::DropJSObjects(this); }
192 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)193 JSObject* VRStageParameters::WrapObject(JSContext* aCx,
194                                         JS::Handle<JSObject*> aGivenProto) {
195   return VRStageParameters_Binding::Wrap(aCx, this, aGivenProto);
196 }
197 
198 NS_IMPL_CYCLE_COLLECTION_CLASS(VRStageParameters)
199 
200 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRStageParameters)
201   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
202   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
203   tmp->mSittingToStandingTransformArray = nullptr;
204 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
205 
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRStageParameters)206 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRStageParameters)
207   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
208 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
209 
210 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRStageParameters)
211   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
212   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(
213       mSittingToStandingTransformArray)
214 NS_IMPL_CYCLE_COLLECTION_TRACE_END
215 
216 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRStageParameters, AddRef)
217 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRStageParameters, Release)
218 
219 void VRStageParameters::GetSittingToStandingTransform(
220     JSContext* aCx, JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv) {
221   if (!mSittingToStandingTransformArray) {
222     // Lazily create the Float32Array
223     mSittingToStandingTransformArray = dom::Float32Array::Create(
224         aCx, this, 16, mSittingToStandingTransform.components);
225     if (!mSittingToStandingTransformArray) {
226       aRv.NoteJSContextException(aCx);
227       return;
228     }
229   }
230   aRetval.set(mSittingToStandingTransformArray);
231 }
232 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRDisplayCapabilities,mParent)233 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRDisplayCapabilities, mParent)
234 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRDisplayCapabilities, AddRef)
235 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRDisplayCapabilities, Release)
236 
237 JSObject* VRDisplayCapabilities::WrapObject(JSContext* aCx,
238                                             JS::Handle<JSObject*> aGivenProto) {
239   return VRDisplayCapabilities_Binding::Wrap(aCx, this, aGivenProto);
240 }
241 
VRPose(nsISupports * aParent,const gfx::VRHMDSensorState & aState)242 VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState)
243     : Pose(aParent), mVRState(aState) {
244   mozilla::HoldJSObjects(this);
245 }
246 
VRPose(nsISupports * aParent)247 VRPose::VRPose(nsISupports* aParent) : Pose(aParent) {
248   mVRState.inputFrameID = 0;
249   mVRState.timestamp = 0.0;
250   mVRState.flags = gfx::VRDisplayCapabilityFlags::Cap_None;
251   mozilla::HoldJSObjects(this);
252 }
253 
~VRPose()254 VRPose::~VRPose() { mozilla::DropJSObjects(this); }
255 
GetPosition(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)256 void VRPose::GetPosition(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
257                          ErrorResult& aRv) {
258   const bool valid =
259       bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) ||
260       bool(mVRState.flags &
261            gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated);
262   SetFloat32Array(aCx, this, aRetval, mPosition,
263                   valid ? mVRState.pose.position : nullptr, 3, aRv);
264 }
265 
GetLinearVelocity(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)266 void VRPose::GetLinearVelocity(JSContext* aCx,
267                                JS::MutableHandle<JSObject*> aRetval,
268                                ErrorResult& aRv) {
269   const bool valid =
270       bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) ||
271       bool(mVRState.flags &
272            gfx::VRDisplayCapabilityFlags::Cap_PositionEmulated);
273   SetFloat32Array(aCx, this, aRetval, mLinearVelocity,
274                   valid ? mVRState.pose.linearVelocity : nullptr, 3, aRv);
275 }
276 
GetLinearAcceleration(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)277 void VRPose::GetLinearAcceleration(JSContext* aCx,
278                                    JS::MutableHandle<JSObject*> aRetval,
279                                    ErrorResult& aRv) {
280   const bool valid = bool(
281       mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration);
282   SetFloat32Array(aCx, this, aRetval, mLinearAcceleration,
283                   valid ? mVRState.pose.linearAcceleration : nullptr, 3, aRv);
284 }
285 
GetOrientation(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)286 void VRPose::GetOrientation(JSContext* aCx,
287                             JS::MutableHandle<JSObject*> aRetval,
288                             ErrorResult& aRv) {
289   const bool valid =
290       bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation);
291   SetFloat32Array(aCx, this, aRetval, mOrientation,
292                   valid ? mVRState.pose.orientation : nullptr, 4, aRv);
293 }
294 
GetAngularVelocity(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)295 void VRPose::GetAngularVelocity(JSContext* aCx,
296                                 JS::MutableHandle<JSObject*> aRetval,
297                                 ErrorResult& aRv) {
298   const bool valid =
299       bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation);
300   SetFloat32Array(aCx, this, aRetval, mAngularVelocity,
301                   valid ? mVRState.pose.angularVelocity : nullptr, 3, aRv);
302 }
303 
GetAngularAcceleration(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)304 void VRPose::GetAngularAcceleration(JSContext* aCx,
305                                     JS::MutableHandle<JSObject*> aRetval,
306                                     ErrorResult& aRv) {
307   const bool valid = bool(
308       mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration);
309   SetFloat32Array(aCx, this, aRetval, mAngularAcceleration,
310                   valid ? mVRState.pose.angularAcceleration : nullptr, 3, aRv);
311 }
312 
Update(const gfx::VRHMDSensorState & aState)313 void VRPose::Update(const gfx::VRHMDSensorState& aState) { mVRState = aState; }
314 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)315 JSObject* VRPose::WrapObject(JSContext* aCx,
316                              JS::Handle<JSObject*> aGivenProto) {
317   return VRPose_Binding::Wrap(aCx, this, aGivenProto);
318 }
319 
320 /* virtual */
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)321 JSObject* VRDisplay::WrapObject(JSContext* aCx,
322                                 JS::Handle<JSObject*> aGivenProto) {
323   return VRDisplay_Binding::Wrap(aCx, this, aGivenProto);
324 }
325 
VRDisplay(nsPIDOMWindowInner * aWindow,gfx::VRDisplayClient * aClient)326 VRDisplay::VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient)
327     : DOMEventTargetHelper(aWindow),
328       mClient(aClient),
329       mDepthNear(0.01f)  // Default value from WebVR Spec
330       ,
331       mDepthFar(10000.0f)  // Default value from WebVR Spec
332       ,
333       mVRNavigationEventDepth(0),
334       mShutdown(false) {
335   const gfx::VRDisplayInfo& info = aClient->GetDisplayInfo();
336   mCapabilities = new VRDisplayCapabilities(aWindow, info.GetCapabilities());
337   if (info.GetCapabilities() &
338       gfx::VRDisplayCapabilityFlags::Cap_StageParameters) {
339     mStageParameters = new VRStageParameters(
340         aWindow, info.GetSittingToStandingTransform(), info.GetStageSize());
341   }
342   mozilla::HoldJSObjects(this);
343   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
344   if (MOZ_LIKELY(obs)) {
345     obs->AddObserver(this, "inner-window-destroyed", false);
346   }
347 }
348 
~VRDisplay()349 VRDisplay::~VRDisplay() {
350   MOZ_ASSERT(mShutdown);
351   mozilla::DropJSObjects(this);
352 }
353 
LastRelease()354 void VRDisplay::LastRelease() {
355   // We don't want to wait for the CC to free up the presentation
356   // for use in other documents, so we do this in LastRelease().
357   Shutdown();
358 }
359 
GetEyeParameters(VREye aEye)360 already_AddRefed<VREyeParameters> VRDisplay::GetEyeParameters(VREye aEye) {
361   gfx::VRDisplayState::Eye eye = aEye == VREye::Left
362                                      ? gfx::VRDisplayState::Eye_Left
363                                      : gfx::VRDisplayState::Eye_Right;
364   RefPtr<VREyeParameters> params = new VREyeParameters(
365       GetParentObject(), mClient->GetDisplayInfo().GetEyeTranslation(eye),
366       mClient->GetDisplayInfo().GetEyeFOV(eye),
367       mClient->GetDisplayInfo().SuggestedEyeResolution());
368   return params.forget();
369 }
370 
Capabilities()371 VRDisplayCapabilities* VRDisplay::Capabilities() { return mCapabilities; }
372 
GetStageParameters()373 VRStageParameters* VRDisplay::GetStageParameters() { return mStageParameters; }
374 
DisplayId() const375 uint32_t VRDisplay::DisplayId() const {
376   const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
377   return info.GetDisplayID();
378 }
379 
GetDisplayName(nsAString & aDisplayName) const380 void VRDisplay::GetDisplayName(nsAString& aDisplayName) const {
381   const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
382   aDisplayName = NS_ConvertUTF8toUTF16(info.GetDisplayName());
383 }
384 
UpdateFrameInfo()385 void VRDisplay::UpdateFrameInfo() {
386   /**
387    * The WebVR 1.1 spec Requires that VRDisplay.getPose and
388    * VRDisplay.getFrameData must return the same values until the next
389    * VRDisplay.submitFrame.
390    *
391    * mFrameInfo is marked dirty at the end of the frame or start of a new
392    * composition and lazily created here in order to receive mid-frame
393    * pose-prediction updates while still ensuring conformance to the WebVR spec
394    * requirements.
395    *
396    * If we are not presenting WebVR content, the frame will never end and we
397    * should return the latest frame data always.
398    */
399   mFrameInfo.Clear();
400 
401   if ((mFrameInfo.IsDirty() && IsPresenting()) ||
402       mClient->GetDisplayInfo().GetPresentingGroups() == 0) {
403     const gfx::VRHMDSensorState& state = mClient->GetSensorState();
404     const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
405     mFrameInfo.Update(info, state, mDepthNear, mDepthFar);
406   }
407 }
408 
GetFrameData(VRFrameData & aFrameData)409 bool VRDisplay::GetFrameData(VRFrameData& aFrameData) {
410   UpdateFrameInfo();
411   if (!(mFrameInfo.mVRState.flags &
412         gfx::VRDisplayCapabilityFlags::Cap_Orientation)) {
413     // We must have at minimum Cap_Orientation for a valid pose.
414     return false;
415   }
416   aFrameData.Update(mFrameInfo);
417   return true;
418 }
419 
GetPose()420 already_AddRefed<VRPose> VRDisplay::GetPose() {
421   UpdateFrameInfo();
422   RefPtr<VRPose> obj = new VRPose(GetParentObject(), mFrameInfo.mVRState);
423 
424   return obj.forget();
425 }
426 
ResetPose()427 void VRDisplay::ResetPose() {
428   // ResetPose is deprecated and unimplemented
429   // We must keep this stub function around as its referenced by
430   // VRDisplay.webidl. Not asserting here, as that could break existing web
431   // content.
432 }
433 
StartVRNavigation()434 void VRDisplay::StartVRNavigation() { mClient->StartVRNavigation(); }
435 
StartHandlingVRNavigationEvent()436 void VRDisplay::StartHandlingVRNavigationEvent() {
437   mHandlingVRNavigationEventStart = TimeStamp::Now();
438   ++mVRNavigationEventDepth;
439   TimeDuration timeout =
440       TimeDuration::FromMilliseconds(StaticPrefs::dom_vr_navigation_timeout());
441   // A 0 or negative TimeDuration indicates that content may take
442   // as long as it wishes to respond to the event, as long as
443   // it happens before the event exits.
444   if (timeout.ToMilliseconds() > 0) {
445     mClient->StopVRNavigation(timeout);
446   }
447 }
448 
StopHandlingVRNavigationEvent()449 void VRDisplay::StopHandlingVRNavigationEvent() {
450   MOZ_ASSERT(mVRNavigationEventDepth > 0);
451   --mVRNavigationEventDepth;
452   if (mVRNavigationEventDepth == 0) {
453     mClient->StopVRNavigation(TimeDuration::FromMilliseconds(0));
454   }
455 }
456 
IsHandlingVRNavigationEvent()457 bool VRDisplay::IsHandlingVRNavigationEvent() {
458   if (mVRNavigationEventDepth == 0) {
459     return false;
460   }
461   if (mHandlingVRNavigationEventStart.IsNull()) {
462     return false;
463   }
464   TimeDuration timeout =
465       TimeDuration::FromMilliseconds(StaticPrefs::dom_vr_navigation_timeout());
466   return timeout.ToMilliseconds() <= 0 ||
467          (TimeStamp::Now() - mHandlingVRNavigationEventStart) <= timeout;
468 }
469 
OnPresentationGenerationChanged()470 void VRDisplay::OnPresentationGenerationChanged() { ExitPresentInternal(); }
471 
RequestPresent(const nsTArray<VRLayer> & aLayers,CallerType aCallerType,ErrorResult & aRv)472 already_AddRefed<Promise> VRDisplay::RequestPresent(
473     const nsTArray<VRLayer>& aLayers, CallerType aCallerType,
474     ErrorResult& aRv) {
475   nsCOMPtr<nsIGlobalObject> global = GetParentObject();
476   if (!global) {
477     aRv.Throw(NS_ERROR_FAILURE);
478     return nullptr;
479   }
480 
481   RefPtr<Promise> promise = Promise::Create(global, aRv);
482   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
483 
484   bool isChromePresentation = aCallerType == CallerType::System;
485   uint32_t presentationGroup =
486       isChromePresentation ? gfx::kVRGroupChrome : gfx::kVRGroupContent;
487 
488   mClient->SetXRAPIMode(gfx::VRAPIMode::WebVR);
489   if (!UserActivation::IsHandlingUserInput() && !isChromePresentation &&
490       !IsHandlingVRNavigationEvent() && StaticPrefs::dom_vr_require_gesture() &&
491       !IsPresenting()) {
492     // The WebVR API states that if called outside of a user gesture, the
493     // promise must be rejected.  We allow VR presentations to start within
494     // trusted events such as vrdisplayactivate, which triggers in response to
495     // HMD proximity sensors and when navigating within a VR presentation.
496     // This user gesture requirement is not enforced for chrome/system code.
497     promise->MaybeRejectWithUndefined();
498   } else if (!IsPresenting() && IsAnyPresenting(presentationGroup)) {
499     // Only one presentation allowed per VRDisplay on a
500     // first-come-first-serve basis.
501     // If this Javascript context is presenting, then we can replace our
502     // presentation with a new one containing new layers but we should never
503     // replace the presentation of another context.
504     // Simultaneous presentations in other groups are allowed in separate
505     // Javascript contexts to enable browser UI from chrome/system contexts.
506     // Eventually, this restriction will be loosened to enable multitasking
507     // use cases.
508     promise->MaybeRejectWithUndefined();
509   } else {
510     if (mPresentation) {
511       mPresentation->UpdateLayers(aLayers);
512     } else {
513       mPresentation = mClient->BeginPresentation(aLayers, presentationGroup);
514     }
515     mFrameInfo.Clear();
516     promise->MaybeResolve(JS::UndefinedHandleValue);
517   }
518   return promise.forget();
519 }
520 
521 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)522 VRDisplay::Observe(nsISupports* aSubject, const char* aTopic,
523                    const char16_t* aData) {
524   MOZ_ASSERT(NS_IsMainThread());
525 
526   if (strcmp(aTopic, "inner-window-destroyed") == 0) {
527     nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
528     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
529 
530     uint64_t innerID;
531     nsresult rv = wrapper->GetData(&innerID);
532     NS_ENSURE_SUCCESS(rv, rv);
533 
534     if (!GetOwner() || GetOwner()->WindowID() == innerID) {
535       Shutdown();
536     }
537 
538     return NS_OK;
539   }
540 
541   // This should not happen.
542   return NS_ERROR_FAILURE;
543 }
544 
ExitPresent(ErrorResult & aRv)545 already_AddRefed<Promise> VRDisplay::ExitPresent(ErrorResult& aRv) {
546   nsCOMPtr<nsIGlobalObject> global = GetParentObject();
547   if (!global) {
548     aRv.Throw(NS_ERROR_FAILURE);
549     return nullptr;
550   }
551 
552   RefPtr<Promise> promise = Promise::Create(global, aRv);
553   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
554 
555   if (!IsPresenting()) {
556     // We can not exit a presentation outside of the context that
557     // started the presentation.
558     promise->MaybeRejectWithUndefined();
559   } else {
560     promise->MaybeResolve(JS::UndefinedHandleValue);
561     ExitPresentInternal();
562   }
563 
564   return promise.forget();
565 }
566 
ExitPresentInternal()567 void VRDisplay::ExitPresentInternal() { mPresentation = nullptr; }
568 
Shutdown()569 void VRDisplay::Shutdown() {
570   mShutdown = true;
571   ExitPresentInternal();
572   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
573   if (MOZ_LIKELY(obs)) {
574     obs->RemoveObserver(this, "inner-window-destroyed");
575   }
576 }
577 
GetLayers(nsTArray<VRLayer> & result)578 void VRDisplay::GetLayers(nsTArray<VRLayer>& result) {
579   if (mPresentation) {
580     mPresentation->GetDOMLayers(result);
581   } else {
582     result = nsTArray<VRLayer>();
583   }
584 }
585 
SubmitFrame()586 void VRDisplay::SubmitFrame() {
587   AUTO_PROFILER_TRACING_MARKER("VR", "SubmitFrameAtVRDisplay", OTHER);
588 
589   if (mClient && !mClient->IsPresentationGenerationCurrent()) {
590     mPresentation = nullptr;
591     mClient->MakePresentationGenerationCurrent();
592   }
593 
594   if (mPresentation) {
595     mPresentation->SubmitFrame();
596   }
597   mFrameInfo.Clear();
598 }
599 
RequestAnimationFrame(FrameRequestCallback & aCallback,ErrorResult & aError)600 int32_t VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback,
601                                          ErrorResult& aError) {
602   if (mShutdown) {
603     return 0;
604   }
605 
606   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
607 
608   int32_t handle;
609   aError = vm->ScheduleFrameRequestCallback(aCallback, &handle);
610   return handle;
611 }
612 
CancelAnimationFrame(int32_t aHandle,ErrorResult & aError)613 void VRDisplay::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError) {
614   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
615   vm->CancelFrameRequestCallback(aHandle);
616 }
617 
IsPresenting() const618 bool VRDisplay::IsPresenting() const {
619   // IsPresenting returns true only if this Javascript context is presenting
620   // and will return false if another context is presenting.
621   return mPresentation != nullptr;
622 }
623 
IsAnyPresenting(uint32_t aGroupMask) const624 bool VRDisplay::IsAnyPresenting(uint32_t aGroupMask) const {
625   // IsAnyPresenting returns true if either this VRDisplay object or any other
626   // from anther Javascript context is presenting with a group matching
627   // aGroupMask.
628   if (mPresentation && (mPresentation->GetGroup() & aGroupMask)) {
629     return true;
630   }
631   if (mClient->GetDisplayInfo().GetPresentingGroups() & aGroupMask) {
632     return true;
633   }
634   return false;
635 }
636 
IsConnected() const637 bool VRDisplay::IsConnected() const { return mClient->GetIsConnected(); }
638 
PresentingGroups() const639 uint32_t VRDisplay::PresentingGroups() const {
640   return mClient->GetDisplayInfo().GetPresentingGroups();
641 }
642 
GroupMask() const643 uint32_t VRDisplay::GroupMask() const {
644   return mClient->GetDisplayInfo().GetGroupMask();
645 }
646 
SetGroupMask(const uint32_t & aGroupMask)647 void VRDisplay::SetGroupMask(const uint32_t& aGroupMask) {
648   mClient->SetGroupMask(aGroupMask);
649 }
650 
651 NS_IMPL_CYCLE_COLLECTION_INHERITED(VRDisplay, DOMEventTargetHelper,
652                                    mCapabilities, mStageParameters)
653 
654 NS_IMPL_ADDREF_INHERITED(VRDisplay, DOMEventTargetHelper)
655 NS_IMPL_RELEASE_INHERITED(VRDisplay, DOMEventTargetHelper)
656 
657 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(VRDisplay)
658   NS_INTERFACE_MAP_ENTRY(nsIObserver)
659   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, DOMEventTargetHelper)
660 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
661 
662 NS_IMPL_CYCLE_COLLECTION_CLASS(VRFrameData)
663 
664 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRFrameData)
665   NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mPose)
666   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
667   tmp->mLeftProjectionMatrix = nullptr;
668   tmp->mLeftViewMatrix = nullptr;
669   tmp->mRightProjectionMatrix = nullptr;
670   tmp->mRightViewMatrix = nullptr;
671 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
672 
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRFrameData)673 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRFrameData)
674   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mPose)
675 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
676 
677 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRFrameData)
678   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
679   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLeftProjectionMatrix)
680   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLeftViewMatrix)
681   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRightProjectionMatrix)
682   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRightViewMatrix)
683 NS_IMPL_CYCLE_COLLECTION_TRACE_END
684 
685 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFrameData, AddRef)
686 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFrameData, Release)
687 
688 VRFrameData::VRFrameData(nsISupports* aParent)
689     : mParent(aParent),
690       mLeftProjectionMatrix(nullptr),
691       mLeftViewMatrix(nullptr),
692       mRightProjectionMatrix(nullptr),
693       mRightViewMatrix(nullptr) {
694   mozilla::HoldJSObjects(this);
695   mPose = new VRPose(aParent);
696 }
697 
~VRFrameData()698 VRFrameData::~VRFrameData() { mozilla::DropJSObjects(this); }
699 
700 /* static */
Constructor(const GlobalObject & aGlobal)701 already_AddRefed<VRFrameData> VRFrameData::Constructor(
702     const GlobalObject& aGlobal) {
703   RefPtr<VRFrameData> obj = new VRFrameData(aGlobal.GetAsSupports());
704   return obj.forget();
705 }
706 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)707 JSObject* VRFrameData::WrapObject(JSContext* aCx,
708                                   JS::Handle<JSObject*> aGivenProto) {
709   return VRFrameData_Binding::Wrap(aCx, this, aGivenProto);
710 }
711 
Pose()712 VRPose* VRFrameData::Pose() { return mPose; }
713 
Timestamp() const714 double VRFrameData::Timestamp() const {
715   // Converting from seconds to milliseconds
716   return mFrameInfo.mVRState.timestamp * 1000.0f;
717 }
718 
GetLeftProjectionMatrix(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)719 void VRFrameData::GetLeftProjectionMatrix(JSContext* aCx,
720                                           JS::MutableHandle<JSObject*> aRetval,
721                                           ErrorResult& aRv) {
722   Pose::SetFloat32Array(aCx, this, aRetval, mLeftProjectionMatrix,
723                         mFrameInfo.mLeftProjection.components, 16, aRv);
724 }
725 
GetLeftViewMatrix(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)726 void VRFrameData::GetLeftViewMatrix(JSContext* aCx,
727                                     JS::MutableHandle<JSObject*> aRetval,
728                                     ErrorResult& aRv) {
729   Pose::SetFloat32Array(aCx, this, aRetval, mLeftViewMatrix,
730                         mFrameInfo.mLeftView.components, 16, aRv);
731 }
732 
GetRightProjectionMatrix(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)733 void VRFrameData::GetRightProjectionMatrix(JSContext* aCx,
734                                            JS::MutableHandle<JSObject*> aRetval,
735                                            ErrorResult& aRv) {
736   Pose::SetFloat32Array(aCx, this, aRetval, mRightProjectionMatrix,
737                         mFrameInfo.mRightProjection.components, 16, aRv);
738 }
739 
GetRightViewMatrix(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval,ErrorResult & aRv)740 void VRFrameData::GetRightViewMatrix(JSContext* aCx,
741                                      JS::MutableHandle<JSObject*> aRetval,
742                                      ErrorResult& aRv) {
743   Pose::SetFloat32Array(aCx, this, aRetval, mRightViewMatrix,
744                         mFrameInfo.mRightView.components, 16, aRv);
745 }
746 
Update(const VRFrameInfo & aFrameInfo)747 void VRFrameData::Update(const VRFrameInfo& aFrameInfo) {
748   mFrameInfo = aFrameInfo;
749   mPose->Update(mFrameInfo.mVRState);
750 }
751 
Update(const gfx::VRDisplayInfo & aInfo,const gfx::VRHMDSensorState & aState,float aDepthNear,float aDepthFar)752 void VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo,
753                          const gfx::VRHMDSensorState& aState, float aDepthNear,
754                          float aDepthFar) {
755   mVRState = aState;
756   if (mTimeStampOffset == 0.0f) {
757     /**
758      * A mTimeStampOffset value of 0.0f indicates that this is the first
759      * iteration and an offset has not yet been set.
760      *
761      * Generate a value for mTimeStampOffset such that if aState.timestamp is
762      * monotonically increasing, aState.timestamp + mTimeStampOffset will never
763      * be a negative number and will start at a pseudo-random offset
764      * between 1000.0f and 11000.0f seconds.
765      *
766      * We use a pseudo random offset rather than 0.0f just to discourage users
767      * from making the assumption that the timestamp returned in the WebVR API
768      * has a base of 0, which is not necessarily true in all UA's.
769      */
770     mTimeStampOffset =
771         float(rand()) / float(RAND_MAX) * 10000.0f + 1000.0f - aState.timestamp;
772   }
773   mVRState.timestamp = aState.timestamp + mTimeStampOffset;
774 
775   // Avoid division by zero within ConstructProjectionMatrix
776   const float kEpsilon = 0.00001f;
777   if (fabs(aDepthFar - aDepthNear) < kEpsilon) {
778     aDepthFar = aDepthNear + kEpsilon;
779   }
780 
781   const gfx::VRFieldOfView leftFOV =
782       aInfo.mDisplayState.eyeFOV[gfx::VRDisplayState::Eye_Left];
783   mLeftProjection =
784       leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
785   const gfx::VRFieldOfView rightFOV =
786       aInfo.mDisplayState.eyeFOV[gfx::VRDisplayState::Eye_Right];
787   mRightProjection =
788       rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
789   memcpy(mLeftView.components, aState.leftViewMatrix,
790          sizeof(aState.leftViewMatrix));
791   memcpy(mRightView.components, aState.rightViewMatrix,
792          sizeof(aState.rightViewMatrix));
793 }
794 
VRFrameInfo()795 VRFrameInfo::VRFrameInfo() : mTimeStampOffset(0.0f) {
796   mVRState.inputFrameID = 0;
797   mVRState.timestamp = 0.0;
798   mVRState.flags = gfx::VRDisplayCapabilityFlags::Cap_None;
799 }
800 
IsDirty()801 bool VRFrameInfo::IsDirty() { return mVRState.timestamp == 0; }
802 
Clear()803 void VRFrameInfo::Clear() { mVRState.Clear(); }
804 
805 }  // namespace dom
806 }  // namespace mozilla
807