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