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