Bug 1323328 - Part 1: Implement VRServiceTest for helping insert VR test data; r?kip, baku draft
authorDaosheng Mu <daoshengmu@gmail.com>
Wed, 01 Mar 2017 18:33:28 +0800
changeset 493250 da904954b72a320c425fea00488c86315ca1d6f0
parent 492625 b23d6277acca34a4b1be9a4c24efd3b999e47ec3
child 493251 f48d74c699fe96402aa6b3ec183d123563ab5e87
push id47700
push userbmo:dmu@mozilla.com
push dateFri, 03 Mar 2017 17:48:54 +0000
reviewerskip, baku
bugs1323328
milestone54.0a1
Bug 1323328 - Part 1: Implement VRServiceTest for helping insert VR test data; r?kip, baku 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
@@ -5039,17 +5039,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);