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