Bug 1323328 - Part 1: Implement VRServiceTest for helping insert VR test data; r=baku,kip
authorDaosheng Mu <daoshengmu@gmail.com>
Wed, 01 Mar 2017 18:33:28 +0800
changeset 394914 41193207cd699f23f1e020bf3f2ec41ef5a1bd87
parent 394913 8fafe2442a7730f3e00dbe7e3546c6b245a74865
child 394915 39365e72a1133d38439489a779c800cc1cca4eb9
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku, kip
bugs1323328
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1323328 - Part 1: Implement VRServiceTest for helping insert VR test data; r=baku,kip MozReview-Commit-ID: 9IoUL6MEVGj
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/vr/VRServiceTest.cpp
dom/vr/VRServiceTest.h
dom/vr/moz.build
dom/webidl/Navigator.webidl
dom/webidl/VRServiceTest.webidl
dom/webidl/moz.build
modules/libpref/init/all.js
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/StorageManager.h"
 #include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VRServiceTest.h"
 #include "mozilla/dom/WebAuthentication.h"
 #include "mozilla/dom/workers/RuntimeService.h"
 #include "mozilla/Hal.h"
 #include "nsISiteSpecificUserAgent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SSE.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
@@ -217,16 +218,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDisplaysPromises)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRServiceTest)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)
 
 void
 Navigator::Invalidate()
 {
   // Don't clear mWindow here so we know we've got a non-null mWindow
@@ -308,16 +310,21 @@ Navigator::Invalidate()
   }
 
   if (mGamepadServiceTest) {
     mGamepadServiceTest->Shutdown();
     mGamepadServiceTest = nullptr;
   }
 
   mVRGetDisplaysPromises.Clear();
+
+  if (mVRServiceTest) {
+    mVRServiceTest->Shutdown();
+    mVRServiceTest = nullptr;
+  }
 }
 
 //*****************************************************************************
 //    Navigator::nsIDOMNavigator
 //*****************************************************************************
 
 void
 Navigator::GetUserAgent(nsAString& aUserAgent, CallerType aCallerType,
@@ -1699,16 +1706,25 @@ Navigator::NotifyVRDisplaysUpdated()
 }
 
 void
 Navigator::NotifyActiveVRDisplaysChanged()
 {
   NavigatorBinding::ClearCachedActiveVRDisplaysValue(this);
 }
 
+VRServiceTest*
+Navigator::RequestVRServiceTest()
+{
+  if (!mVRServiceTest) {
+    mVRServiceTest = VRServiceTest::CreateTestService(mWindow);
+  }
+  return mVRServiceTest;
+}
+
 //*****************************************************************************
 //    Navigator::nsIMozNavigatorNetwork
 //*****************************************************************************
 
 NS_IMETHODIMP
 Navigator::GetProperties(nsINetworkProperties** aProperties)
 {
   ErrorResult rv;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -74,16 +74,17 @@ namespace network {
 class Connection;
 } // namespace network
 
 class PowerManager;
 class DeviceStorageAreaListener;
 class Presentation;
 class LegacyMozTCPSocket;
 class VRDisplay;
+class VRServiceTest;
 class StorageManager;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
@@ -212,16 +213,17 @@ public:
   already_AddRefed<LegacyMozTCPSocket> MozTCPSocket();
   network::Connection* GetConnection(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
 
   void GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
   GamepadServiceTest* RequestGamepadServiceTest();
   already_AddRefed<Promise> GetVRDisplays(ErrorResult& aRv);
   void GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const;
+  VRServiceTest* RequestVRServiceTest();
 #ifdef MOZ_TIME_MANAGER
   time::TimeManager* GetMozTime(ErrorResult& aRv);
 #endif // MOZ_TIME_MANAGER
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
   system::AudioChannelManager* GetMozAudioChannelManager(ErrorResult& aRv);
 #endif // MOZ_AUDIO_CHANNEL_MANAGER
 
   Presentation* GetPresentation(ErrorResult& aRv);
@@ -324,16 +326,17 @@ private:
   nsTArray<nsWeakPtr> mDeviceStorageStores;
   RefPtr<time::TimeManager> mTimeManager;
   RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener;
   RefPtr<Presentation> mPresentation;
   RefPtr<GamepadServiceTest> mGamepadServiceTest;
   nsTArray<RefPtr<Promise> > mVRGetDisplaysPromises;
+  RefPtr<VRServiceTest> mVRServiceTest;
   nsTArray<uint32_t> mRequestedVibrationPattern;
   RefPtr<StorageManager> mStorageManager;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Navigator_h
new file mode 100644
--- /dev/null
+++ b/dom/vr/VRServiceTest.cpp
@@ -0,0 +1,351 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/VRServiceTest.h"
+#include "mozilla/dom/VRServiceTestBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRMockDisplay)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRMockDisplay,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VRMockDisplay,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRMockDisplay)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(VRMockDisplay, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRMockDisplay, DOMEventTargetHelper)
+
+VRMockDisplay::VRMockDisplay(const nsCString& aID, uint32_t aDeviceID)
+ : mDeviceID(aDeviceID)
+{
+  mDisplayInfo.mDisplayName = aID;
+  mDisplayInfo.mType = VRDeviceType::Puppet;
+  mDisplayInfo.mIsConnected = true;
+  mDisplayInfo.mIsMounted = false;
+  mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
+                                  VRDisplayCapabilityFlags::Cap_Orientation |
+                                  VRDisplayCapabilityFlags::Cap_AngularAcceleration |
+                                  VRDisplayCapabilityFlags::Cap_Position |
+                                  VRDisplayCapabilityFlags::Cap_LinearAcceleration |
+                                  VRDisplayCapabilityFlags::Cap_External |
+                                  VRDisplayCapabilityFlags::Cap_Present |
+                                  VRDisplayCapabilityFlags::Cap_StageParameters |
+                                  VRDisplayCapabilityFlags::Cap_MountDetection;
+}
+
+JSObject*
+VRMockDisplay::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return VRMockDisplayBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void VRMockDisplay::SetEyeResolution(unsigned long aRenderWidth, unsigned long aRenderHeight)
+{
+  mDisplayInfo.mEyeResolution.width = aRenderWidth;
+  mDisplayInfo.mEyeResolution.height = aRenderHeight;
+}
+
+void
+VRMockDisplay::SetEyeParameter(VREye aEye, double aOffsetX, double aOffsetY,
+                               double aOffsetZ, double aUpDegree, double aRightDegree,
+                               double aDownDegree, double aLeftDegree)
+{
+  uint32_t eye = static_cast<uint32_t>(aEye);
+  mDisplayInfo.mEyeFOV[eye] = gfx ::VRFieldOfView(aUpDegree, aRightDegree,
+                                                  aRightDegree, aLeftDegree);
+  mDisplayInfo.mEyeTranslation[eye].x = aOffsetX;
+  mDisplayInfo.mEyeTranslation[eye].y = aOffsetY;
+  mDisplayInfo.mEyeTranslation[eye].z = aOffsetZ;
+}
+
+void
+VRMockDisplay::SetPose(const Nullable<Float32Array>& aPosition,
+                       const Nullable<Float32Array>& aLinearVelocity,
+                       const Nullable<Float32Array>& aLinearAcceleration,
+                       const Nullable<Float32Array>& aOrientation,
+                       const Nullable<Float32Array>& aAngularVelocity,
+                       const Nullable<Float32Array>& aAngularAcceleration)
+{
+  mSensorState.flags = VRDisplayCapabilityFlags::Cap_Orientation |
+                       VRDisplayCapabilityFlags::Cap_Position |
+                       VRDisplayCapabilityFlags::Cap_AngularAcceleration |
+                       VRDisplayCapabilityFlags::Cap_LinearAcceleration |
+                       VRDisplayCapabilityFlags::Cap_External |
+                       VRDisplayCapabilityFlags::Cap_MountDetection |
+                       VRDisplayCapabilityFlags::Cap_Present;
+
+  if (!aOrientation.IsNull()) {
+    const Float32Array& value = aOrientation.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 4);
+    mSensorState.orientation[0] = value.Data()[0];
+    mSensorState.orientation[1] = value.Data()[1];
+    mSensorState.orientation[2] = value.Data()[2];
+    mSensorState.orientation[3] = value.Data()[3];
+  }
+  if (!aAngularVelocity.IsNull()) {
+    const Float32Array& value = aAngularVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.angularVelocity[0] = value.Data()[0];
+    mSensorState.angularVelocity[1] = value.Data()[1];
+    mSensorState.angularVelocity[2] = value.Data()[2];
+  }
+  if (!aAngularAcceleration.IsNull()) {
+    const Float32Array& value = aAngularAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.angularAcceleration[0] = value.Data()[0];
+    mSensorState.angularAcceleration[1] = value.Data()[1];
+    mSensorState.angularAcceleration[2] = value.Data()[2];
+  }
+  if (!aPosition.IsNull()) {
+    const Float32Array& value = aPosition.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.position[0] = value.Data()[0];
+    mSensorState.position[1] = value.Data()[1];
+    mSensorState.position[2] = value.Data()[2];
+  }
+  if (!aLinearVelocity.IsNull()) {
+    const Float32Array& value = aLinearVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.linearVelocity[0] = value.Data()[0];
+    mSensorState.linearVelocity[1] = value.Data()[1];
+    mSensorState.linearVelocity[2] = value.Data()[2];
+  }
+  if (!aLinearAcceleration.IsNull()) {
+    const Float32Array& value = aLinearAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    mSensorState.linearAcceleration[0] = value.Data()[0];
+    mSensorState.linearAcceleration[1] = value.Data()[1];
+    mSensorState.linearAcceleration[2] = value.Data()[2];
+  }
+}
+
+void
+VRMockDisplay::Update()
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  MOZ_ASSERT(vm);
+
+  vm->SendSetSensorStateToMockDisplay(mDeviceID, mSensorState);
+  vm->SendSetDisplayInfoToMockDisplay(mDeviceID, mDisplayInfo);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRMockController)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRMockController,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VRMockController,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRMockController)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(VRMockController, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRMockController, DOMEventTargetHelper)
+
+VRMockController::VRMockController(const nsCString& aID, uint32_t aDeviceID)
+ : mID(aID), mDeviceID(aDeviceID)
+{
+}
+
+JSObject*
+VRMockController::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return VRMockControllerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+VRMockController::NewButtonEvent(unsigned long aButton, bool aPressed)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  MOZ_ASSERT(vm);
+  vm->SendNewButtonEventToMockController(mDeviceID, aButton, aPressed);
+}
+
+void
+VRMockController::NewAxisMoveEvent(unsigned long aAxis, double aValue)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  MOZ_ASSERT(vm);
+  vm->SendNewAxisMoveEventToMockController(mDeviceID, aAxis, aValue);
+}
+
+void
+VRMockController::NewPoseMove(const Nullable<Float32Array>& aPosition,
+                              const Nullable<Float32Array>& aLinearVelocity,
+                              const Nullable<Float32Array>& aLinearAcceleration,
+                              const Nullable<Float32Array>& aOrientation,
+                              const Nullable<Float32Array>& aAngularVelocity,
+                              const Nullable<Float32Array>& aAngularAcceleration)
+{
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  MOZ_ASSERT(vm);
+
+  GamepadPoseState poseState;
+  poseState.flags = GamepadCapabilityFlags::Cap_Orientation |
+                    GamepadCapabilityFlags::Cap_Position |
+                    GamepadCapabilityFlags::Cap_AngularAcceleration |
+                    GamepadCapabilityFlags::Cap_LinearAcceleration;
+  if (!aOrientation.IsNull()) {
+    const Float32Array& value = aOrientation.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 4);
+    poseState.orientation[0] = value.Data()[0];
+    poseState.orientation[1] = value.Data()[1];
+    poseState.orientation[2] = value.Data()[2];
+    poseState.orientation[3] = value.Data()[3];
+  }
+  if (!aPosition.IsNull()) {
+    const Float32Array& value = aPosition.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.position[0] = value.Data()[0];
+    poseState.position[1] = value.Data()[1];
+    poseState.position[2] = value.Data()[2];
+  }
+  if (!aAngularVelocity.IsNull()) {
+    const Float32Array& value = aAngularVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.angularVelocity[0] = value.Data()[0];
+    poseState.angularVelocity[1] = value.Data()[1];
+    poseState.angularVelocity[2] = value.Data()[2];
+  }
+  if (!aAngularAcceleration.IsNull()) {
+    const Float32Array& value = aAngularAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.angularAcceleration[0] = value.Data()[0];
+    poseState.angularAcceleration[1] = value.Data()[1];
+    poseState.angularAcceleration[2] = value.Data()[2];
+  }
+  if (!aLinearVelocity.IsNull()) {
+    const Float32Array& value = aLinearVelocity.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.linearVelocity[0] = value.Data()[0];
+    poseState.linearVelocity[1] = value.Data()[1];
+    poseState.linearVelocity[2] = value.Data()[2];
+  }
+  if (!aLinearAcceleration.IsNull()) {
+    const Float32Array& value = aLinearAcceleration.Value();
+    value.ComputeLengthAndData();
+    MOZ_ASSERT(value.Length() == 3);
+    poseState.linearAcceleration[0] = value.Data()[0];
+    poseState.linearAcceleration[1] = value.Data()[1];
+    poseState.linearAcceleration[2] = value.Data()[2];
+  }
+  vm->SendNewPoseMoveToMockController(mDeviceID, poseState);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRServiceTest)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRServiceTest,
+                                                  DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(VRServiceTest,
+                                                DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRServiceTest)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(VRServiceTest, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRServiceTest, DOMEventTargetHelper)
+
+
+JSObject*
+VRServiceTest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return VRServiceTestBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+already_AddRefed<VRServiceTest>
+VRServiceTest::CreateTestService(nsPIDOMWindowInner* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  RefPtr<VRServiceTest> service = new VRServiceTest(aWindow);
+  return service.forget();
+}
+
+VRServiceTest::VRServiceTest(nsPIDOMWindowInner* aWindow)
+  : mWindow(aWindow),
+    mShuttingDown(false)
+{}
+
+VRServiceTest::~VRServiceTest()
+{}
+
+void
+VRServiceTest::Shutdown()
+{
+  MOZ_ASSERT(!mShuttingDown);
+  mShuttingDown = true;
+  mWindow = nullptr;
+}
+
+already_AddRefed<Promise>
+VRServiceTest::AttachVRDisplay(const nsAString& aID, ErrorResult& aRv)
+{
+  if (mShuttingDown) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+
+  RefPtr<Promise> p = Promise::Create(go, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  MOZ_ASSERT(vm);
+  vm->CreateVRServiceTestDisplay(nsCString(ToNewUTF8String(aID)), p);
+
+  return p.forget();
+}
+
+already_AddRefed<Promise>
+VRServiceTest::AttachVRController(const nsAString& aID, ErrorResult& aRv)
+{
+  if (mShuttingDown) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+
+  RefPtr<Promise> p = Promise::Create(go, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+  MOZ_ASSERT(vm);
+  vm->CreateVRServiceTestController(nsCString(ToNewUTF8String(aID)), p);
+
+  return p.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/vr/VRServiceTest.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_VRServiceTest_h_
+#define mozilla_dom_VRServiceTest_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/VRServiceTestBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+class VRMockDisplay final : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRMockDisplay, DOMEventTargetHelper)
+
+  VRMockDisplay(const nsCString& aID, uint32_t aDeviceID);
+  void SetEyeParameter(VREye aEye, double aOffsetX, double aOffsetY, double aOffsetZ,
+                       double aUpDegree, double aRightDegree,
+                       double aDownDegree, double aLeftDegree);
+  void SetEyeResolution(unsigned long aRenderWidth, unsigned long aRenderHeight);
+  void SetPose(const Nullable<Float32Array>& aPosition, const Nullable<Float32Array>& aLinearVelocity,
+               const Nullable<Float32Array>& aLinearAcceleration, const Nullable<Float32Array>& aOrientation,
+               const Nullable<Float32Array>& aAngularVelocity, const Nullable<Float32Array>& aAngularAcceleration);
+  void Update();
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  ~VRMockDisplay() = default;
+
+  uint32_t mDeviceID;
+  gfx::VRDisplayInfo mDisplayInfo;
+  gfx::VRHMDSensorState mSensorState;
+};
+
+class VRMockController : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRMockController, DOMEventTargetHelper)
+
+  VRMockController(const nsCString& aID, uint32_t aDeviceID);
+  void NewButtonEvent(unsigned long aButton, bool aPressed);
+  void NewAxisMoveEvent(unsigned long aAxis, double aValue);
+  void NewPoseMove(const Nullable<Float32Array>& aPosition, const Nullable<Float32Array>& aLinearVelocity,
+                   const Nullable<Float32Array>& aLinearAcceleration, const Nullable<Float32Array>& aOrientation,
+                   const Nullable<Float32Array>& aAngularVelocity, const Nullable<Float32Array>& aAngularAcceleration);
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  ~VRMockController() = default;
+
+  nsCString mID;
+  uint32_t mDeviceID;
+};
+
+class VRServiceTest final : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRServiceTest, DOMEventTargetHelper)
+
+  already_AddRefed<Promise> AttachVRDisplay(const nsAString& aID, ErrorResult& aRv);
+  already_AddRefed<Promise> AttachVRController(const nsAString& aID, ErrorResult& aRv);
+  void Shutdown();
+
+  static already_AddRefed<VRServiceTest> CreateTestService(nsPIDOMWindowInner* aWindow);
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  explicit VRServiceTest(nsPIDOMWindowInner* aWindow);
+  ~VRServiceTest();
+
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  bool mShuttingDown;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_VRServiceTest_h_
\ No newline at end of file
--- a/dom/vr/moz.build
+++ b/dom/vr/moz.build
@@ -6,22 +6,24 @@
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM")
 
 EXPORTS.mozilla.dom += [
     'VRDisplay.h',
     'VRDisplayEvent.h',
     'VREventObserver.h',
+    'VRServiceTest.h'
     ]
 
 UNIFIED_SOURCES = [
     'VRDisplay.cpp',
     'VRDisplayEvent.cpp',
     'VREventObserver.cpp',
+    'VRServiceTest.cpp'
     ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base'
 ]
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -284,16 +284,20 @@ partial interface Navigator {
 
 partial interface Navigator {
   [Throws, Pref="dom.vr.enabled"]
   Promise<sequence<VRDisplay>> getVRDisplays();
   // TODO: Use FrozenArray once available. (Bug 1236777)
   [Frozen, Cached, Pure, Pref="dom.vr.enabled"]
   readonly attribute sequence<VRDisplay> activeVRDisplays;
 };
+partial interface Navigator {
+  [Pref="dom.vr.test.enabled"]
+  VRServiceTest requestVRServiceTest();
+};
 
 #ifdef MOZ_TIME_MANAGER
 // nsIDOMMozNavigatorTime
 partial interface Navigator {
   [Throws, ChromeOnly, UnsafeInPrerendering]
   readonly attribute MozTimeManager mozTime;
 };
 #endif // MOZ_TIME_MANAGER
new file mode 100644
--- /dev/null
+++ b/dom/webidl/VRServiceTest.webidl
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This WebIDL is just for WebVR testing.
+ */
+
+[Pref="dom.vr.test.enabled",
+ HeaderFile="mozilla/dom/VRServiceTest.h"]
+interface VRMockDisplay {
+  void setEyeResolution(unsigned long aRenderWidth, unsigned long aRenderHeight);
+  void setEyeParameter(VREye eye, double offsetX, double offsetY, double offsetZ,
+                       double upDegree, double rightDegree,
+                       double downDegree, double leftDegree);
+  void setPose(Float32Array? position, Float32Array? linearVelocity,
+               Float32Array? linearAcceleration, Float32Array? orientation,
+               Float32Array? angularVelocity, Float32Array? angularAcceleration);
+  void update();
+};
+
+[Pref="dom.vr.test.enabled",
+ HeaderFile="mozilla/dom/VRServiceTest.h"]
+interface VRMockController {
+  void newButtonEvent(unsigned long button, boolean pressed);
+  void newAxisMoveEvent(unsigned long axis, double value);
+  void newPoseMove(Float32Array? position, Float32Array? linearVelocity,
+                   Float32Array? linearAcceleration, Float32Array? orientation,
+                   Float32Array? angularVelocity, Float32Array? angularAcceleration);
+};
+
+[Pref="dom.vr.test.enabled",
+ HeaderFile="mozilla/dom/VRServiceTest.h"]
+interface VRServiceTest {
+  [Throws, NewObject]
+  Promise<VRMockDisplay> attachVRDisplay(DOMString id);
+  [Throws, NewObject]
+  Promise<VRMockController> attachVRController(DOMString id);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -930,16 +930,17 @@ WEBIDL_FILES = [
     'URLSearchParams.webidl',
     'ValidityState.webidl',
     'VideoPlaybackQuality.webidl',
     'VideoStreamTrack.webidl',
     'VideoTrack.webidl',
     'VideoTrackList.webidl',
     'VRDisplay.webidl',
     'VRDisplayEvent.webidl',
+    'VRServiceTest.webidl',
     'VTTCue.webidl',
     'VTTRegion.webidl',
     'WaveShaperNode.webidl',
     'WebAuthentication.webidl',
     'WebComponents.webidl',
     'WebGL2RenderingContext.webidl',
     'WebGLRenderingContext.webidl',
     'WebKitCSSMatrix.webidl',
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5031,17 +5031,17 @@ pref("dom.vr.openvr.enabled", false);
 pref("dom.vr.poseprediction.enabled", false);
 // path to openvr DLL
 pref("gfx.vr.openvr-runtime", "");
 // path to OSVR DLLs
 pref("gfx.vr.osvr.utilLibPath", "");
 pref("gfx.vr.osvr.commonLibPath", "");
 pref("gfx.vr.osvr.clientLibPath", "");
 pref("gfx.vr.osvr.clientKitLibPath", "");
-
+pref("dom.vr.test.enabled", false);
 // MMS UA Profile settings
 pref("wap.UAProf.url", "");
 pref("wap.UAProf.tagname", "x-wap-profile");
 
 // MMS version 1.1 = 0x11 (or decimal 17)
 // MMS version 1.3 = 0x13 (or decimal 19)
 // @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.34
 pref("dom.mms.version", 19);