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