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