Bug 1466702 - Refactor / reimplement gfxVRPuppet and VRServiceTest to use gfxVRExternal r=daoshengmu,thomasmo,bzbarsky
authorKearwood "Kip" Gilbert <kgilbert@mozilla.com>
Fri, 28 Jun 2019 21:19:54 +0000
changeset 480620 133ac0d11428079ceaeceab4dda050b4ba240e3b
parent 480619 321be77f2c08bd572a09d03ec37d122f27a1e002
child 480621 f66816f02d28b130a1b2e17957955fc367083ce7
push id36216
push userncsoregi@mozilla.com
push dateSat, 29 Jun 2019 09:58:05 +0000
treeherdermozilla-central@41dbf1cd1fbf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaoshengmu, thomasmo, bzbarsky
bugs1466702
milestone69.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 1466702 - Refactor / reimplement gfxVRPuppet and VRServiceTest to use gfxVRExternal r=daoshengmu,thomasmo,bzbarsky gfxVRPuppet will be replaced with a fully asynchronous puppet automation that runs in the VR process. Differential Revision: https://phabricator.services.mozilla.com/D26263
dom/vr/VRDisplay.cpp
dom/vr/VRDisplay.h
dom/vr/VRServiceTest.cpp
dom/vr/VRServiceTest.h
dom/vr/test/mochitest/mochitest.ini
dom/vr/test/reftest/reftest.list
dom/webidl/Navigator.webidl
dom/webidl/VRDisplay.webidl
dom/webidl/VRServiceTest.webidl
gfx/vr/VRDisplayClient.cpp
gfx/vr/VRDisplayClient.h
gfx/vr/VRDisplayHost.cpp
gfx/vr/VRDisplayHost.h
gfx/vr/VRDisplayLocal.cpp
gfx/vr/VRDisplayLocal.h
gfx/vr/VRManager.cpp
gfx/vr/VRManager.h
gfx/vr/VRPuppetCommandBuffer.cpp
gfx/vr/VRPuppetCommandBuffer.h
gfx/vr/VRServiceHost.cpp
gfx/vr/VRServiceHost.h
gfx/vr/external_api/moz_external_vr.h
gfx/vr/gfxVR.cpp
gfx/vr/gfxVR.h
gfx/vr/gfxVRExternal.cpp
gfx/vr/gfxVRExternal.h
gfx/vr/gfxVRPuppet.cpp
gfx/vr/gfxVRPuppet.h
gfx/vr/ipc/PVRManager.ipdl
gfx/vr/ipc/VRChild.cpp
gfx/vr/ipc/VRGPUChild.cpp
gfx/vr/ipc/VRLayerParent.cpp
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerChild.h
gfx/vr/ipc/VRManagerParent.cpp
gfx/vr/ipc/VRManagerParent.h
gfx/vr/ipc/VRProcessParent.cpp
gfx/vr/moz.build
gfx/vr/service/PuppetSession.cpp
gfx/vr/service/PuppetSession.h
gfx/vr/service/VRService.cpp
gfx/vr/service/VRService.h
gfx/vr/service/VRServiceManager.cpp
gfx/vr/service/VRServiceManager.h
gfx/vr/service/moz.build
modules/libpref/init/StaticPrefList.h
modules/libpref/init/all.js
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -393,56 +393,29 @@ bool VRDisplay::GetFrameData(VRFrameData
         gfx::VRDisplayCapabilityFlags::Cap_Orientation)) {
     // We must have at minimum Cap_Orientation for a valid pose.
     return false;
   }
   aFrameData.Update(mFrameInfo);
   return true;
 }
 
-bool VRDisplay::GetSubmitFrameResult(VRSubmitFrameResult& aResult) {
-  if (!mPresentation) {
-    return false;
-  }
-
-  VRSubmitFrameResultInfo resultInfo;
-  mClient->GetSubmitFrameResult(resultInfo);
-  if (!resultInfo.mBase64Image.Length()) {
-    return false;  // The submit frame result is not ready.
-  }
-
-  nsAutoCString decodedImg;
-  if (Base64Decode(resultInfo.mBase64Image, decodedImg) != NS_OK) {
-    MOZ_ASSERT(false, "Failed to do decode base64 images.");
-    return false;
-  }
-
-  const char* srcData = decodedImg.get();
-  const gfx::IntSize size(resultInfo.mWidth, resultInfo.mHeight);
-  RefPtr<DataSourceSurface> dataSurface = gfx::CreateDataSourceSurfaceFromData(
-      size, resultInfo.mFormat, (uint8_t*)srcData,
-      StrideForFormatAndWidth(resultInfo.mFormat, resultInfo.mWidth));
-  if (!dataSurface || !dataSurface->IsValid()) {
-    MOZ_ASSERT(false, "dataSurface is null.");
-    return false;
-  }
-
-  nsAutoCString encodedImg(gfxUtils::GetAsDataURI(dataSurface));
-  aResult.Update(resultInfo.mFrameNum, encodedImg);
-  return true;
-}
-
 already_AddRefed<VRPose> VRDisplay::GetPose() {
   UpdateFrameInfo();
   RefPtr<VRPose> obj = new VRPose(GetParentObject(), mFrameInfo.mVRState);
 
   return obj.forget();
 }
 
-void VRDisplay::ResetPose() { mClient->ZeroSensor(); }
+void VRDisplay::ResetPose() {
+  // ResetPose is deprecated and unimplemented
+  // We must keep this stub function around as its referenced by
+  // VRDisplay.webidl. Not asserting here, as that could break existing web
+  // content.
+}
 
 void VRDisplay::StartVRNavigation() { mClient->StartVRNavigation(); }
 
 void VRDisplay::StartHandlingVRNavigationEvent() {
   mHandlingVRNavigationEventStart = TimeStamp::Now();
   ++mVRNavigationEventDepth;
   TimeDuration timeout =
       TimeDuration::FromMilliseconds(StaticPrefs::dom_vr_navigation_timeout());
@@ -823,46 +796,10 @@ VRFrameInfo::VRFrameInfo() : mTimeStampO
   mVRState.timestamp = 0.0;
   mVRState.flags = gfx::VRDisplayCapabilityFlags::Cap_None;
 }
 
 bool VRFrameInfo::IsDirty() { return mVRState.timestamp == 0; }
 
 void VRFrameInfo::Clear() { mVRState.Clear(); }
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRSubmitFrameResult, mParent)
-NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRSubmitFrameResult, AddRef)
-NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRSubmitFrameResult, Release)
-
-VRSubmitFrameResult::VRSubmitFrameResult(nsISupports* aParent)
-    : mParent(aParent), mFrameNum(0) {
-  mozilla::HoldJSObjects(this);
-}
-
-VRSubmitFrameResult::~VRSubmitFrameResult() { mozilla::DropJSObjects(this); }
-
-/* static */
-already_AddRefed<VRSubmitFrameResult> VRSubmitFrameResult::Constructor(
-    const GlobalObject& aGlobal, ErrorResult& aRv) {
-  RefPtr<VRSubmitFrameResult> obj =
-      new VRSubmitFrameResult(aGlobal.GetAsSupports());
-  return obj.forget();
-}
-
-JSObject* VRSubmitFrameResult::WrapObject(JSContext* aCx,
-                                          JS::Handle<JSObject*> aGivenProto) {
-  return VRSubmitFrameResult_Binding::Wrap(aCx, this, aGivenProto);
-}
-
-void VRSubmitFrameResult::Update(uint64_t aFrameNum,
-                                 const nsACString& aBase64Image) {
-  mFrameNum = aFrameNum;
-  mBase64Image = NS_ConvertASCIItoUTF16(aBase64Image);
-}
-
-double VRSubmitFrameResult::FrameNum() const { return mFrameNum; }
-
-void VRSubmitFrameResult::GetBase64Image(nsAString& aImage) const {
-  aImage = mBase64Image;
-}
-
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -250,43 +250,16 @@ class VREyeParameters final : public nsW
   nsCOMPtr<nsISupports> mParent;
 
   gfx::Point3D mEyeTranslation;
   gfx::IntSize mRenderSize;
   JS::Heap<JSObject*> mOffset;
   RefPtr<VRFieldOfView> mFOV;
 };
 
-class VRSubmitFrameResult final : public nsWrapperCache {
- public:
-  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRSubmitFrameResult)
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRSubmitFrameResult)
-
-  explicit VRSubmitFrameResult(nsISupports* aParent);
-  static already_AddRefed<VRSubmitFrameResult> Constructor(
-      const GlobalObject& aGlobal, ErrorResult& aRv);
-
-  void Update(uint64_t aFrameNum, const nsACString& aBase64Image);
-  // WebIDL Members
-  double FrameNum() const;
-  void GetBase64Image(nsAString& aImage) const;
-
-  // WebIDL Boilerplate
-  nsISupports* GetParentObject() const { return mParent; }
-  virtual JSObject* WrapObject(JSContext* aCx,
-                               JS::Handle<JSObject*> aGivenProto) override;
-
- protected:
-  ~VRSubmitFrameResult();
-
-  nsCOMPtr<nsISupports> mParent;
-  nsString mBase64Image;
-  uint64_t mFrameNum;
-};
-
 class VRDisplay final : public DOMEventTargetHelper, public nsIObserver {
  public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIOBSERVER
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRDisplay, DOMEventTargetHelper)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
@@ -310,17 +283,16 @@ class VRDisplay final : public DOMEventT
   static void UpdateVRDisplays(nsTArray<RefPtr<VRDisplay> >& aDisplays,
                                nsPIDOMWindowInner* aWindow);
 
   gfx::VRDisplayClient* GetClient() { return mClient; }
 
   virtual already_AddRefed<VREyeParameters> GetEyeParameters(VREye aEye);
 
   bool GetFrameData(VRFrameData& aFrameData);
-  bool GetSubmitFrameResult(VRSubmitFrameResult& aResult);
   already_AddRefed<VRPose> GetPose();
   void ResetPose();
 
   double DepthNear() { return mDepthNear; }
 
   double DepthFar() { return mDepthFar; }
 
   void SetDepthNear(double aDepthNear) {
--- a/dom/vr/VRServiceTest.cpp
+++ b/dom/vr/VRServiceTest.cpp
@@ -1,18 +1,21 @@
 /* -*- 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"
+#include "VRPuppetCommandBuffer.h"
+#include <type_traits>
 
 namespace mozilla {
+using namespace gfx;
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(VRMockDisplay)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(VRMockDisplay,
                                                   DOMEventTargetHelper)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
@@ -21,235 +24,535 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(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{},
-      mSensorState{},
-      mTimestamp(TimeStamp::Now()) {
-  VRDisplayState& state = mDisplayInfo.mDisplayState;
-  strncpy(state.displayName, aID.BeginReading(), kVRDisplayNameMaxLen);
-  mDisplayInfo.mType = VRDeviceType::Puppet;
-  state.isConnected = true;
-  state.isMounted = false;
-  state.capabilityFlags = 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;
+namespace {
+template <class T>
+bool ReadFloat32Array(T& aDestination, const Float32Array& aSource,
+                      ErrorResult& aRv) {
+  constexpr size_t length = std::extent<T>::value;
+  aSource.ComputeLengthAndData();
+  if (aSource.Length() != length) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    // We don't want to MOZ_ASSERT here, as that would cause the
+    // browser to crash, making it difficult to debug the problem
+    // in JS code calling this API.
+    return false;
+  }
+  for (size_t i = 0; i < length; i++) {
+    aDestination[i] = aSource.Data()[i];
+  }
+  return true;
 }
+};  // anonymous namespace
+
+VRMockDisplay::VRMockDisplay(VRServiceTest* aVRServiceTest)
+    : DOMEventTargetHelper(aVRServiceTest->GetOwner()),
+      mVRServiceTest(aVRServiceTest) {}
 
 JSObject* VRMockDisplay::WrapObject(JSContext* aCx,
                                     JS::Handle<JSObject*> aGivenProto) {
   return VRMockDisplay_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-void VRMockDisplay::SetEyeResolution(unsigned long aRenderWidth,
-                                     unsigned long aRenderHeight) {
-  mDisplayInfo.mDisplayState.eyeResolution.width = aRenderWidth;
-  mDisplayInfo.mDisplayState.eyeResolution.height = aRenderHeight;
+VRHMDSensorState& VRMockDisplay::SensorState() const {
+  return mVRServiceTest->SystemState().sensorState;
+}
+
+VRDisplayState& VRMockDisplay::DisplayState() const {
+  return mVRServiceTest->SystemState().displayState;
+}
+
+void VRMockDisplay::Clear() {
+  VRDisplayState& displayState = DisplayState();
+  displayState.Clear();
+  VRHMDSensorState& sensorState = SensorState();
+  sensorState.Clear();
 }
 
-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.mDisplayState.eyeFOV[eye] =
-      gfx ::VRFieldOfView(aUpDegree, aRightDegree, aRightDegree, aLeftDegree);
-  mDisplayInfo.mDisplayState.eyeTranslation[eye].x = aOffsetX;
-  mDisplayInfo.mDisplayState.eyeTranslation[eye].y = aOffsetY;
-  mDisplayInfo.mDisplayState.eyeTranslation[eye].z = aOffsetZ;
+void VRMockDisplay::Create() {
+  Clear();
+  VRDisplayState& state = DisplayState();
+
+  strncpy(state.displayName, "Puppet HMD", kVRDisplayNameMaxLen);
+  state.eightCC = GFX_VR_EIGHTCC('P', 'u', 'p', 'p', 'e', 't', ' ', ' ');
+  state.isConnected = true;
+  state.isMounted = false;
+  state.capabilityFlags = VRDisplayCapabilityFlags::Cap_None |
+                          VRDisplayCapabilityFlags::Cap_Orientation |
+                          VRDisplayCapabilityFlags::Cap_Position |
+                          VRDisplayCapabilityFlags::Cap_External |
+                          VRDisplayCapabilityFlags::Cap_Present |
+                          VRDisplayCapabilityFlags::Cap_StageParameters |
+                          VRDisplayCapabilityFlags::Cap_MountDetection;
+
+  // 1836 x 2040 resolution is arbitrary and can be overridden.
+  // This default resolution was chosen to be within range of a
+  // typical VR eye buffer size.  This value is derived by
+  // scaling a 1080x1200 per-eye panel resolution by the
+  // commonly used pre-lens-distortion pass scaling factor of 1.7x.
+  // 1.7x is commonly used in HMD's employing fresnel lenses to ensure
+  // a sufficient fragment shading rate in the peripheral area of the
+  // post-warp eye buffers.
+  state.eyeResolution.width = 1836;   // 1080 * 1.7
+  state.eyeResolution.height = 2040;  // 1200 * 1.7
+
+  for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; ++eye) {
+    state.eyeTranslation[eye].x = 0.0f;
+    state.eyeTranslation[eye].y = 0.0f;
+    state.eyeTranslation[eye].z = 0.0f;
+    state.eyeFOV[eye] = gfx::VRFieldOfView(45.0, 45.0, 45.0, 45.0);
+  }
+
+  // default: 1m x 1m space, 0.75m high in seated position
+  state.stageSize.width = 1.0f;
+  state.stageSize.height = 1.0f;
+
+  state.sittingToStandingTransform[0] = 1.0f;
+  state.sittingToStandingTransform[1] = 0.0f;
+  state.sittingToStandingTransform[2] = 0.0f;
+  state.sittingToStandingTransform[3] = 0.0f;
+
+  state.sittingToStandingTransform[4] = 0.0f;
+  state.sittingToStandingTransform[5] = 1.0f;
+  state.sittingToStandingTransform[6] = 0.0f;
+  state.sittingToStandingTransform[7] = 0.0f;
+
+  state.sittingToStandingTransform[8] = 0.0f;
+  state.sittingToStandingTransform[9] = 0.0f;
+  state.sittingToStandingTransform[10] = 1.0f;
+  state.sittingToStandingTransform[11] = 0.0f;
+
+  state.sittingToStandingTransform[12] = 0.0f;
+  state.sittingToStandingTransform[13] = 0.75f;
+  state.sittingToStandingTransform[14] = 0.0f;
+  state.sittingToStandingTransform[15] = 1.0f;
+
+  VRHMDSensorState& sensorState = SensorState();
+  gfx::Quaternion rot;
+  sensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
+  sensorState.pose.orientation[0] = rot.x;
+  sensorState.pose.orientation[1] = rot.y;
+  sensorState.pose.orientation[2] = rot.z;
+  sensorState.pose.orientation[3] = rot.w;
+  sensorState.pose.angularVelocity[0] = 0.0f;
+  sensorState.pose.angularVelocity[1] = 0.0f;
+  sensorState.pose.angularVelocity[2] = 0.0f;
+
+  sensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
+  sensorState.pose.position[0] = 0.0f;
+  sensorState.pose.position[1] = 0.0f;
+  sensorState.pose.position[2] = 0.0f;
+  sensorState.pose.linearVelocity[0] = 0.0f;
+  sensorState.pose.linearVelocity[1] = 0.0f;
+  sensorState.pose.linearVelocity[2] = 0.0f;
+}
+
+void VRMockDisplay::SetConnected(bool aConnected) {
+  DisplayState().isConnected = aConnected;
+}
+bool VRMockDisplay::Connected() const { return DisplayState().isConnected; }
+
+void VRMockDisplay::SetMounted(bool aMounted) {
+  DisplayState().isMounted = aMounted;
+}
+
+bool VRMockDisplay::Mounted() const { return DisplayState().isMounted; }
+
+void VRMockDisplay::SetCapFlag(VRDisplayCapabilityFlags aFlag, bool aEnabled) {
+  if (aEnabled) {
+    DisplayState().capabilityFlags |= aFlag;
+  } else {
+    DisplayState().capabilityFlags &= ~aFlag;
+  }
+}
+bool VRMockDisplay::GetCapFlag(VRDisplayCapabilityFlags aFlag) const {
+  return ((DisplayState().capabilityFlags & aFlag) !=
+          VRDisplayCapabilityFlags::Cap_None);
+}
+
+void VRMockDisplay::SetCapPosition(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_Position, aEnabled);
 }
 
-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.Clear();
-  mSensorState.timestamp = (TimeStamp::Now() - mTimestamp).ToSeconds();
-  mSensorState.flags = VRDisplayCapabilityFlags::Cap_Orientation |
-                       VRDisplayCapabilityFlags::Cap_Position |
-                       VRDisplayCapabilityFlags::Cap_AngularAcceleration |
-                       VRDisplayCapabilityFlags::Cap_LinearAcceleration |
-                       VRDisplayCapabilityFlags::Cap_External |
-                       VRDisplayCapabilityFlags::Cap_MountDetection |
-                       VRDisplayCapabilityFlags::Cap_Present;
+void VRMockDisplay::SetCapOrientation(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_Orientation, aEnabled);
+}
+
+void VRMockDisplay::SetCapPresent(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_Present, aEnabled);
+}
+
+void VRMockDisplay::SetCapExternal(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_External, aEnabled);
+}
+
+void VRMockDisplay::SetCapAngularAcceleration(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_AngularAcceleration, aEnabled);
+}
+
+void VRMockDisplay::SetCapLinearAcceleration(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_LinearAcceleration, aEnabled);
+}
+
+void VRMockDisplay::SetCapStageParameters(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_StageParameters, aEnabled);
+}
+
+void VRMockDisplay::SetCapMountDetection(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_MountDetection, aEnabled);
+}
+
+void VRMockDisplay::SetCapPositionEmulated(bool aEnabled) {
+  SetCapFlag(VRDisplayCapabilityFlags::Cap_PositionEmulated, aEnabled);
+}
+
+void VRMockDisplay::SetEyeFOV(VREye aEye, double aUpDegree, double aRightDegree,
+                              double aDownDegree, double aLeftDegree) {
+  gfx::VRDisplayState::Eye eye = aEye == VREye::Left
+                                     ? gfx::VRDisplayState::Eye_Left
+                                     : gfx::VRDisplayState::Eye_Right;
+  VRDisplayState& state = DisplayState();
+  state.eyeFOV[eye] =
+      gfx::VRFieldOfView(aUpDegree, aRightDegree, aDownDegree, aLeftDegree);
+}
+
+void VRMockDisplay::SetEyeOffset(VREye aEye, double aOffsetX, double aOffsetY,
+                                 double aOffsetZ) {
+  gfx::VRDisplayState::Eye eye = aEye == VREye::Left
+                                     ? gfx::VRDisplayState::Eye_Left
+                                     : gfx::VRDisplayState::Eye_Right;
+  VRDisplayState& state = DisplayState();
+  state.eyeTranslation[eye].x = (float)aOffsetX;
+  state.eyeTranslation[eye].y = (float)aOffsetY;
+  state.eyeTranslation[eye].z = (float)aOffsetZ;
+}
+
+bool VRMockDisplay::CapPosition() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_Position);
+}
+
+bool VRMockDisplay::CapOrientation() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_Orientation);
+}
+
+bool VRMockDisplay::CapPresent() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_Present);
+}
+
+bool VRMockDisplay::CapExternal() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_External);
+}
+
+bool VRMockDisplay::CapAngularAcceleration() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_AngularAcceleration);
+}
+
+bool VRMockDisplay::CapLinearAcceleration() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_LinearAcceleration);
+}
+
+bool VRMockDisplay::CapStageParameters() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_StageParameters);
+}
+
+bool VRMockDisplay::CapMountDetection() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_MountDetection);
+}
+
+bool VRMockDisplay::CapPositionEmulated() const {
+  return GetCapFlag(VRDisplayCapabilityFlags::Cap_PositionEmulated);
+}
+
+void VRMockDisplay::SetEyeResolution(uint32_t aRenderWidth,
+                                     uint32_t aRenderHeight) {
+  DisplayState().eyeResolution.width = aRenderWidth;
+  DisplayState().eyeResolution.height = aRenderHeight;
+}
+
+void VRMockDisplay::SetStageSize(double aWidth, double aHeight) {
+  VRDisplayState& displayState = DisplayState();
+  displayState.stageSize.width = (float)aWidth;
+  displayState.stageSize.height = (float)aHeight;
+}
+
+void VRMockDisplay::SetSittingToStandingTransform(
+    const Float32Array& aTransform, ErrorResult& aRv) {
+  Unused << ReadFloat32Array(DisplayState().sittingToStandingTransform,
+                             aTransform, aRv);
+}
+
+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,
+                            ErrorResult& aRv) {
+  VRHMDSensorState& sensorState = mVRServiceTest->SystemState().sensorState;
+  sensorState.Clear();
+  sensorState.flags = VRDisplayCapabilityFlags::Cap_None;
+  // sensorState.timestamp will be set automatically during
+  // puppet script execution
 
   if (!aOrientation.IsNull()) {
-    const Float32Array& value = aOrientation.Value();
-    value.ComputeLengthAndData();
-    MOZ_ASSERT(value.Length() == 4);
-    mSensorState.pose.orientation[0] = value.Data()[0];
-    mSensorState.pose.orientation[1] = value.Data()[1];
-    mSensorState.pose.orientation[2] = value.Data()[2];
-    mSensorState.pose.orientation[3] = value.Data()[3];
+    if (!ReadFloat32Array(sensorState.pose.orientation, aOrientation.Value(),
+                          aRv)) {
+      return;
+    }
+    sensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
   }
   if (!aAngularVelocity.IsNull()) {
-    const Float32Array& value = aAngularVelocity.Value();
-    value.ComputeLengthAndData();
-    MOZ_ASSERT(value.Length() == 3);
-    mSensorState.pose.angularVelocity[0] = value.Data()[0];
-    mSensorState.pose.angularVelocity[1] = value.Data()[1];
-    mSensorState.pose.angularVelocity[2] = value.Data()[2];
+    if (!ReadFloat32Array(sensorState.pose.angularVelocity,
+                          aAngularVelocity.Value(), aRv)) {
+      return;
+    }
+    sensorState.flags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration;
   }
   if (!aAngularAcceleration.IsNull()) {
-    const Float32Array& value = aAngularAcceleration.Value();
-    value.ComputeLengthAndData();
-    MOZ_ASSERT(value.Length() == 3);
-    mSensorState.pose.angularAcceleration[0] = value.Data()[0];
-    mSensorState.pose.angularAcceleration[1] = value.Data()[1];
-    mSensorState.pose.angularAcceleration[2] = value.Data()[2];
+    if (!ReadFloat32Array(sensorState.pose.angularAcceleration,
+                          aAngularAcceleration.Value(), aRv)) {
+      return;
+    }
+    sensorState.flags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration;
   }
   if (!aPosition.IsNull()) {
-    const Float32Array& value = aPosition.Value();
-    value.ComputeLengthAndData();
-    MOZ_ASSERT(value.Length() == 3);
-    mSensorState.pose.position[0] = value.Data()[0];
-    mSensorState.pose.position[1] = value.Data()[1];
-    mSensorState.pose.position[2] = value.Data()[2];
+    if (!ReadFloat32Array(sensorState.pose.position, aPosition.Value(), aRv)) {
+      return;
+    }
+    sensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
   }
   if (!aLinearVelocity.IsNull()) {
-    const Float32Array& value = aLinearVelocity.Value();
-    value.ComputeLengthAndData();
-    MOZ_ASSERT(value.Length() == 3);
-    mSensorState.pose.linearVelocity[0] = value.Data()[0];
-    mSensorState.pose.linearVelocity[1] = value.Data()[1];
-    mSensorState.pose.linearVelocity[2] = value.Data()[2];
+    if (!ReadFloat32Array(sensorState.pose.linearVelocity,
+                          aLinearVelocity.Value(), aRv)) {
+      return;
+    }
+    sensorState.flags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration;
   }
   if (!aLinearAcceleration.IsNull()) {
-    const Float32Array& value = aLinearAcceleration.Value();
-    value.ComputeLengthAndData();
-    MOZ_ASSERT(value.Length() == 3);
-    mSensorState.pose.linearAcceleration[0] = value.Data()[0];
-    mSensorState.pose.linearAcceleration[1] = value.Data()[1];
-    mSensorState.pose.linearAcceleration[2] = value.Data()[2];
+    if (!ReadFloat32Array(sensorState.pose.linearAcceleration,
+                          aLinearAcceleration.Value(), aRv)) {
+      return;
+    }
+    sensorState.flags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration;
   }
 }
 
-void VRMockDisplay::Update() {
-  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-
-  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(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) {}
+VRMockController::VRMockController(VRServiceTest* aVRServiceTest,
+                                   uint32_t aControllerIdx)
+    : DOMEventTargetHelper(aVRServiceTest->GetOwner()),
+      mControllerIdx(aControllerIdx) {
+  MOZ_ASSERT(aControllerIdx < kVRControllerMaxCount);
+}
 
 JSObject* VRMockController::WrapObject(JSContext* aCx,
                                        JS::Handle<JSObject*> aGivenProto) {
   return VRMockController_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-void VRMockController::NewButtonEvent(unsigned long aButton, bool aPressed) {
-  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->SendNewButtonEventToMockController(mDeviceID, aButton, aPressed);
+VRControllerState& VRMockController::ControllerState() const {
+  return mVRServiceTest->SystemState().controllerState[mControllerIdx];
+}
+
+void VRMockController::Create() {
+  // Initialize with a 6dof, left-handed gamepad with one haptic actuator
+  // Tests are expected to modify the controller before it is sent to the
+  // puppet.
+  Clear();
+  VRControllerState& state = ControllerState();
+  strncpy(state.controllerName, "Puppet Gamepad", kVRControllerNameMaxLen);
+  state.hand = GamepadHand::Left;
+  state.flags = GamepadCapabilityFlags::Cap_Position |
+                GamepadCapabilityFlags::Cap_Orientation;
+  state.numButtons = 1;
+  state.numHaptics = 1;
+  state.triggerValue[0] = 0.0f;
+}
+
+void VRMockController::Clear() {
+  mVRServiceTest->ClearController(mControllerIdx);
+}
+
+void VRMockController::SetCapFlag(GamepadCapabilityFlags aFlag, bool aEnabled) {
+  if (aEnabled) {
+    ControllerState().flags |= aFlag;
+  } else {
+    ControllerState().flags &= ~aFlag;
+  }
+}
+bool VRMockController::GetCapFlag(GamepadCapabilityFlags aFlag) const {
+  return (ControllerState().flags & aFlag) != GamepadCapabilityFlags::Cap_None;
+}
+
+void VRMockController::SetHand(GamepadHand aHand) {
+  ControllerState().hand = aHand;
+}
+
+GamepadHand VRMockController::Hand() const { return ControllerState().hand; }
+
+void VRMockController::SetCapPosition(bool aEnabled) {
+  SetCapFlag(GamepadCapabilityFlags::Cap_Position, aEnabled);
+}
+
+bool VRMockController::CapPosition() const {
+  return GetCapFlag(GamepadCapabilityFlags::Cap_Position);
+}
+
+void VRMockController::SetCapOrientation(bool aEnabled) {
+  SetCapFlag(GamepadCapabilityFlags::Cap_Orientation, aEnabled);
+}
+
+bool VRMockController::CapOrientation() const {
+  return GetCapFlag(GamepadCapabilityFlags::Cap_Orientation);
+}
+
+void VRMockController::SetCapAngularAcceleration(bool aEnabled) {
+  SetCapFlag(GamepadCapabilityFlags::Cap_AngularAcceleration, aEnabled);
+}
+
+bool VRMockController::CapAngularAcceleration() const {
+  return GetCapFlag(GamepadCapabilityFlags::Cap_AngularAcceleration);
 }
 
-void VRMockController::NewAxisMoveEvent(unsigned long aAxis, double aValue) {
-  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->SendNewAxisMoveEventToMockController(mDeviceID, aAxis, aValue);
+void VRMockController::SetCapLinearAcceleration(bool aEnabled) {
+  SetCapFlag(GamepadCapabilityFlags::Cap_LinearAcceleration, aEnabled);
+}
+
+bool VRMockController::CapLinearAcceleration() const {
+  return GetCapFlag(GamepadCapabilityFlags::Cap_LinearAcceleration);
+}
+
+void VRMockController::SetAxisCount(uint32_t aCount) {
+  MOZ_ASSERT(aCount <= kVRControllerMaxAxis);
+  ControllerState().numAxes = aCount;
+}
+
+uint32_t VRMockController::AxisCount() const {
+  return ControllerState().numAxes;
+}
+
+void VRMockController::SetButtonCount(uint32_t aCount) {
+  MOZ_ASSERT(aCount <= kVRControllerMaxButtons);
+  ControllerState().numButtons = aCount;
+}
+
+uint32_t VRMockController::ButtonCount() const {
+  return ControllerState().numButtons;
+}
+
+void VRMockController::SetHapticCount(uint32_t aCount) {
+  ControllerState().numHaptics = aCount;
 }
 
-void VRMockController::NewPoseMove(
+uint32_t VRMockController::HapticCount() const {
+  return ControllerState().numHaptics;
+}
+
+void VRMockController::SetButtonPressed(uint32_t aButtonIdx, bool aPressed) {
+  MOZ_ASSERT(aButtonIdx < kVRControllerMaxButtons);
+  if (aPressed) {
+    ControllerState().buttonPressed |= (1 << aButtonIdx);
+  } else {
+    ControllerState().buttonPressed &= ~(1 << aButtonIdx);
+  }
+}
+
+void VRMockController::SetButtonTouched(uint32_t aButtonIdx, bool aTouched) {
+  MOZ_ASSERT(aButtonIdx < kVRControllerMaxButtons);
+  if (aTouched) {
+    ControllerState().buttonTouched |= (1 << aButtonIdx);
+  } else {
+    ControllerState().buttonTouched &= ~(1 << aButtonIdx);
+  }
+}
+
+void VRMockController::SetButtonTrigger(uint32_t aButtonIdx, double aTrigger) {
+  MOZ_ASSERT(aButtonIdx < kVRControllerMaxButtons);
+
+  ControllerState().triggerValue[aButtonIdx] = (float)aTrigger;
+}
+
+void VRMockController::SetAxisValue(uint32_t aAxisIdx, double aValue) {
+  MOZ_ASSERT(aAxisIdx < kVRControllerMaxAxis);
+  ControllerState().axisValue[aAxisIdx] = (float)aValue;
+}
+
+void VRMockController::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) {
-  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  GamepadPoseState poseState;
+    const Nullable<Float32Array>& aAngularAcceleration, ErrorResult& aRv) {
+  VRControllerState& controllerState = ControllerState();
+  controllerState.flags = GamepadCapabilityFlags::Cap_None;
 
-  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];
-    poseState.isOrientationValid = true;
-  }
-  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];
-    poseState.isPositionValid = true;
+    if (!ReadFloat32Array(controllerState.pose.orientation,
+                          aOrientation.Value(), aRv)) {
+      return;
+    }
+    controllerState.flags |= GamepadCapabilityFlags::Cap_Orientation;
   }
   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 (!ReadFloat32Array(controllerState.pose.angularVelocity,
+                          aAngularVelocity.Value(), aRv)) {
+      return;
+    }
+    controllerState.flags |= GamepadCapabilityFlags::Cap_AngularAcceleration;
   }
   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 (!ReadFloat32Array(controllerState.pose.angularAcceleration,
+                          aAngularAcceleration.Value(), aRv)) {
+      return;
+    }
+    controllerState.flags |= GamepadCapabilityFlags::Cap_AngularAcceleration;
+  }
+  if (!aPosition.IsNull()) {
+    if (!ReadFloat32Array(controllerState.pose.position, aPosition.Value(),
+                          aRv)) {
+      return;
+    }
+    controllerState.flags |= GamepadCapabilityFlags::Cap_Position;
   }
   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 (!ReadFloat32Array(controllerState.pose.linearVelocity,
+                          aLinearVelocity.Value(), aRv)) {
+      return;
+    }
+    controllerState.flags |= GamepadCapabilityFlags::Cap_LinearAcceleration;
   }
   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];
+    if (!ReadFloat32Array(controllerState.pose.linearAcceleration,
+                          aLinearAcceleration.Value(), aRv)) {
+      return;
+    }
+    controllerState.flags |= GamepadCapabilityFlags::Cap_LinearAcceleration;
   }
-  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
 
@@ -272,55 +575,198 @@ JSObject* VRServiceTest::WrapObject(JSCo
 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) {
-  gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->SendCreateVRTestSystem();
+    : mWindow(aWindow), mPendingState{}, mEncodedState{}, mShuttingDown(false) {
+  mDisplay = new VRMockDisplay(this);
+  for (int i = 0; i < kVRControllerMaxCount; i++) {
+    mControllers.AppendElement(new VRMockController(this, i));
+  }
+  ClearAll();
+}
+
+gfx::VRSystemState& VRServiceTest::SystemState() { return mPendingState; }
+
+VRMockDisplay* VRServiceTest::GetVRDisplay() { return mDisplay; }
+
+VRMockController* VRServiceTest::GetVRController(uint32_t aControllerIdx,
+                                                 ErrorResult& aRv) {
+  if (aControllerIdx >= kVRControllerMaxCount) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return nullptr;
+  }
+  return mControllers[aControllerIdx];
 }
 
 void VRServiceTest::Shutdown() {
   MOZ_ASSERT(!mShuttingDown);
   mShuttingDown = true;
   mWindow = nullptr;
 }
 
-already_AddRefed<Promise> VRServiceTest::AttachVRDisplay(const nsAString& aID,
-                                                         ErrorResult& aRv) {
+void VRServiceTest::AddCommand(uint64_t aCommand) {
+  EncodeData();
+  mCommandBuffer.AppendElement(aCommand);
+}
+
+void VRServiceTest::End() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_End);
+}
+
+void VRServiceTest::ClearAll() {
+  memset(&mPendingState, 0, sizeof(VRSystemState));
+  memset(&mEncodedState, 0, sizeof(VRSystemState));
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_ClearAll);
+}
+
+void VRServiceTest::ClearController(uint32_t aControllerIdx) {
+  MOZ_ASSERT(aControllerIdx < kVRControllerMaxCount);
+  mPendingState.controllerState[aControllerIdx].Clear();
+  mEncodedState.controllerState[aControllerIdx].Clear();
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_ClearController |
+             (uint64_t)aControllerIdx);
+}
+
+void VRServiceTest::Timeout(uint32_t aDuration) {
+  // Clamp to 32-bit unsigned value
+  if (aDuration > 0xffffffff) {
+    aDuration = 0xffffffff;
+  }
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_Timeout |
+             (uint64_t)aDuration);
+}
+
+void VRServiceTest::Wait(uint32_t aDuration) {
+  // Clamp to 32-bit unsigned value
+  if (aDuration > 0xffffffff) {
+    aDuration = 0xffffffff;
+  }
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_Wait | (uint64_t)aDuration);
+}
+
+void VRServiceTest::WaitHapticIntensity(uint32_t aControllerIdx,
+                                        uint32_t aHapticIdx, double aIntensity,
+                                        ErrorResult& aRv) {
+  if (aControllerIdx >= kVRControllerMaxCount) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+  if (aHapticIdx >= kVRHapticsMaxCount) {
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+  // convert to 16.16 fixed point.  This must match conversion in
+  // VRPuppetCommandBuffer::RunCommand
+  uint64_t iIntensity = round((float)aIntensity * (1 << 16));
+  if (iIntensity > 0xffffffff) {
+    iIntensity = 0xffffffff;
+  }
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_WaitHapticIntensity |
+             ((uint64_t)aControllerIdx << 40) | ((uint64_t)aHapticIdx << 32) |
+             iIntensity);
+}
+
+void VRServiceTest::WaitSubmit() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_WaitSubmit);
+}
+
+void VRServiceTest::WaitPresentationStart() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_WaitPresentationStart);
+}
+void VRServiceTest::WaitPresentationEnd() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_WaitPresentationEnd);
+}
+
+void VRServiceTest::EncodeData() {
+  VRPuppetCommandBuffer::EncodeStruct(
+      mCommandBuffer, (uint8_t*)&mPendingState.displayState,
+      (uint8_t*)&mEncodedState.displayState, sizeof(VRDisplayState),
+      VRPuppet_Command::VRPuppet_UpdateDisplay);
+  VRPuppetCommandBuffer::EncodeStruct(
+      mCommandBuffer, (uint8_t*)&mPendingState.sensorState,
+      (uint8_t*)&mEncodedState.sensorState, sizeof(VRHMDSensorState),
+      VRPuppet_Command::VRPuppet_UpdateSensor);
+  VRPuppetCommandBuffer::EncodeStruct(
+      mCommandBuffer, (uint8_t*)&mPendingState.controllerState,
+      (uint8_t*)&mEncodedState.controllerState,
+      sizeof(VRControllerState) * kVRControllerMaxCount,
+      VRPuppet_Command::VRPuppet_UpdateControllers);
+}
+
+void VRServiceTest::CaptureFrame() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_CaptureFrame);
+}
+
+void VRServiceTest::AcknowledgeFrame() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_AcknowledgeFrame);
+}
+
+void VRServiceTest::RejectFrame() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_RejectFrame);
+}
+
+void VRServiceTest::StartTimer() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_StartTimer);
+}
+
+void VRServiceTest::StopTimer() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_StopTimer);
+}
+
+void VRServiceTest::Commit() {
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_Commit);
+}
+
+already_AddRefed<Promise> VRServiceTest::Run(ErrorResult& aRv) {
   if (mShuttingDown) {
     return nullptr;
   }
 
-  RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+  AddCommand((uint64_t)VRPuppet_Command::VRPuppet_End);
+
+  RefPtr<dom::Promise> runPuppetPromise =
+      Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->CreateVRServiceTestDisplay(NS_ConvertUTF16toUTF8(aID), p);
+  vm->RunPuppet(mCommandBuffer, runPuppetPromise, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
 
-  return p.forget();
+  mCommandBuffer.Clear();
+
+  return runPuppetPromise.forget();
 }
 
-already_AddRefed<Promise> VRServiceTest::AttachVRController(
-    const nsAString& aID, ErrorResult& aRv) {
+already_AddRefed<Promise> VRServiceTest::Reset(ErrorResult& aRv) {
   if (mShuttingDown) {
     return nullptr;
   }
 
-  RefPtr<Promise> p = Promise::Create(mWindow->AsGlobal(), aRv);
+  RefPtr<dom::Promise> resetPuppetPromise =
+      Promise::Create(mWindow->AsGlobal(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-  vm->CreateVRServiceTestController(NS_ConvertUTF16toUTF8(aID), p);
+  vm->ResetPuppet(resetPuppetPromise, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
 
-  return p.forget();
+  memset(&mPendingState, 0, sizeof(VRSystemState));
+  memset(&mEncodedState, 0, sizeof(VRSystemState));
+  mCommandBuffer.Clear();
+
+  return resetPuppetPromise.forget();
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/vr/VRServiceTest.h
+++ b/dom/vr/VRServiceTest.h
@@ -8,96 +8,198 @@
 #define mozilla_dom_VRServiceTest_h_
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/VRServiceTestBinding.h"
 
 #include "gfxVR.h"
 
 namespace mozilla {
+namespace gfx {
+enum class VRDisplayCapabilityFlags : uint16_t;
+enum class VRPuppet_Command : uint64_t;
+}  // namespace gfx
 namespace dom {
+enum class GamepadCapabilityFlags : uint16_t;
 
 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);
+  explicit VRMockDisplay(VRServiceTest* aVRServiceTest);
+
+  void Create();
+  void Clear();
+
+  void SetConnected(bool aConnected);
+  bool Connected() const;
+  void SetMounted(bool aMounted);
+  bool Mounted() const;
+  void SetCapPosition(bool aEnabled);
+  bool CapPosition() const;
+  void SetCapOrientation(bool aEnabled);
+  bool CapOrientation() const;
+  void SetCapPresent(bool aEnabled);
+  bool CapPresent() const;
+  void SetCapExternal(bool aEnabled);
+  bool CapExternal() const;
+  void SetCapAngularAcceleration(bool aEnabled);
+  bool CapAngularAcceleration() const;
+  void SetCapLinearAcceleration(bool aEnabled);
+  bool CapLinearAcceleration() const;
+  void SetCapStageParameters(bool aEnabled);
+  bool CapStageParameters() const;
+  void SetCapMountDetection(bool aEnabled);
+  bool CapMountDetection() const;
+  void SetCapPositionEmulated(bool aEnabled);
+  bool CapPositionEmulated() const;
+  void SetEyeFOV(VREye aEye, double aUpDegree, double aRightDegree,
+                 double aDownDegree, double aLeftDegree);
+  void SetEyeOffset(VREye aEye, double aOffsetX, double aOffsetY,
+                    double aOffsetZ);
+  void SetEyeResolution(uint32_t aRenderWidth, uint32_t aRenderHeight);
+  void SetStageSize(double aWidth, double aHeight);
+  void SetSittingToStandingTransform(const Float32Array& aTransform,
+                                     ErrorResult& aRv);
   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 SetMountState(bool aIsMounted) {
-    mDisplayInfo.mDisplayState.isMounted = aIsMounted;
-  }
-  void Update();
+               const Nullable<Float32Array>& aAngularAcceleration,
+               ErrorResult& aRv);
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
  private:
   ~VRMockDisplay() = default;
+  gfx::VRDisplayState& DisplayState() const;
+  gfx::VRHMDSensorState& SensorState() const;
+  void SetCapFlag(gfx::VRDisplayCapabilityFlags aFlag, bool aEnabled);
+  bool GetCapFlag(gfx::VRDisplayCapabilityFlags aFlag) const;
 
-  uint32_t mDeviceID;
-  gfx::VRDisplayInfo mDisplayInfo;
-  gfx::VRHMDSensorState mSensorState;
-  TimeStamp mTimestamp;
+  RefPtr<VRServiceTest> mVRServiceTest;
 };
 
 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);
+  VRMockController(VRServiceTest* aVRServiceTest, uint32_t aControllerIdx);
+  void Create();
+  void Clear();
+  void SetHand(GamepadHand aHand);
+  GamepadHand Hand() const;
+  void SetCapPosition(bool aEnabled);
+  bool CapPosition() const;
+  void SetCapOrientation(bool aEnabled);
+  bool CapOrientation() const;
+  void SetCapAngularAcceleration(bool aEnabled);
+  bool CapAngularAcceleration() const;
+  void SetCapLinearAcceleration(bool aEnabled);
+  bool CapLinearAcceleration() const;
+  void SetAxisCount(uint32_t aCount);
+  uint32_t AxisCount() const;
+  void SetButtonCount(uint32_t aCount);
+  uint32_t ButtonCount() const;
+  void SetHapticCount(uint32_t aCount);
+  uint32_t HapticCount() const;
+  void SetButtonPressed(uint32_t aButtonIdx, bool aPressed);
+  void SetButtonTouched(uint32_t aButtonIdx, bool aTouched);
+  void SetButtonTrigger(uint32_t aButtonIdx, double aTrigger);
+  void SetAxisValue(uint32_t aAxisIdx, double aValue);
+  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,
+               ErrorResult& aRv);
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
  private:
   ~VRMockController() = default;
-
-  nsCString mID;
-  uint32_t mDeviceID;
+  gfx::VRControllerState& ControllerState() const;
+  void SetCapFlag(GamepadCapabilityFlags aFlag, bool aEnabled);
+  bool GetCapFlag(GamepadCapabilityFlags aFlag) const;
+  RefPtr<VRServiceTest> mVRServiceTest;
+  uint32_t mControllerIdx;
 };
 
 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);
+  // WebIDL interface
+
+  void ClearAll();
+  void ClearController(uint32_t aControllerIdx);
+  void Commit();
+  void End();
+  already_AddRefed<Promise> Run(ErrorResult& aRv);
+  already_AddRefed<Promise> Reset(ErrorResult& aRv);
+  VRMockDisplay* GetVRDisplay();
+  VRMockController* GetVRController(uint32_t aControllerIdx, ErrorResult& aRv);
+  void Timeout(uint32_t aDuration);
+  void Wait(uint32_t aDuration);
+  void WaitSubmit();
+  void WaitPresentationStart();
+  void WaitPresentationEnd();
+  void WaitHapticIntensity(uint32_t aControllerIdx, uint32_t aHapticIdx,
+                           double aIntensity, ErrorResult& aRv);
+  void CaptureFrame();
+  void AcknowledgeFrame();
+  void RejectFrame();
+  void StartTimer();
+  void StopTimer();
+
+  // Implementation
   void Shutdown();
-
+  void AddCommand(uint64_t aCommand);
   static already_AddRefed<VRServiceTest> CreateTestService(
       nsPIDOMWindowInner* aWindow);
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
+  gfx::VRSystemState& SystemState();
 
  private:
   explicit VRServiceTest(nsPIDOMWindowInner* aWindow);
   ~VRServiceTest() = default;
+  void EncodeData();
 
+  RefPtr<VRMockDisplay> mDisplay;
+  nsTArray<RefPtr<VRMockController>> mControllers;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  // mPendingState records the state of the emulated VR hardware, including
+  // changes that have not yet been committed to the command buffer.
+  gfx::VRSystemState mPendingState;
+  // mEncodedState records the state of the emulate VR hardware at the end
+  // of the last committed transaction, submitted with VRServiceTest::Commit().
+  // mPendingState represents the resulting state if all of the commands in
+  // mCommandBuffer have been played back.
+  gfx::VRSystemState mEncodedState;
+  // mCommandBuffer encodes a sequence of steps to be executed asynchronously by
+  // the simulated VR hardware.  The steps are encoded as a stream of uint64's,
+  // using the format described in gfx/vr/VRPuppetCommandBuffer.h
+  // mCommandBuffer includes only complete transactions, which will be played
+  // back such that multiple values in VRSystemState will be updated atomically.
+  // When the command buffer is submitted to the PuppetSession, with
+  // VRServiceTest::Run(), it is cleared  to ensure that the commands are not
+  // sent redundantly in subsequent VRServicetest::Run() calls.
+  // VRServiceTest::Commit() will perform a binary comparison of mPendingState
+  // and mEncodedState to determine what instructions need to be appended to
+  // mCommandBuffer.
+  // VRServiceTest::Reset() will effectively cancel all transactions and clear
+  // mCommandBuffer before submitting the reset request to the PuppetSession.
+  InfallibleTArray<uint64_t> mCommandBuffer;
   bool mShuttingDown;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_VRServiceTest_h_
--- a/dom/vr/test/mochitest/mochitest.ini
+++ b/dom/vr/test/mochitest/mochitest.ini
@@ -4,29 +4,29 @@ support-files =
   VRSimulationDriver.js
   requestPresent.js
   runVRTest.js
   WebVRHelpers.js
 
 [test_vrController_displayId.html]
 # Enable Linux after Bug 1310655 # TIMED_OUT for Android.
 # skip-if = (os != "win" && release_or_beta) || (os == "android")
-# Re-enable this once Bug 1466702 has landed
+# Dependencies for re-enabling these are tracked by meta bug 1555185.
 skip-if = true
 [test_vrDisplay_canvas2d.html]
 # skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
-# Re-enable this once Bug 1466702 has landed
+# Dependencies for re-enabling these are tracked by meta bug 1555185.
 skip-if = true
 [test_vrDisplay_exitPresent.html]
 # skip-if = (os != "win" && release_or_beta) # Enable Linux after Bug 1310655
-# Re-enable this once Bug 1466702 has landed
+# Dependencies for re-enabling these are tracked by meta bug 1555185.
 skip-if = true
 [test_vrDisplay_getFrameData.html]
 # Enable Linux after Bug 1310655, enable Android after Bug 1348246
 # skip-if = (os != "win" && release_or_beta) || (os == "android")
-# Re-enable this once Bug 1466702 has landed
+# Dependencies for re-enabling these are tracked by meta bug 1555185.
 skip-if = true
 [test_vrDisplay_onvrdisplayconnect.html]
 skip-if = true
 [test_vrDisplay_onvrdisplaydeactivate_crosscontent.html]
 skip-if = true
 [test_vrDisplay_requestPresent.html]
 skip-if = true
--- a/dom/vr/test/reftest/reftest.list
+++ b/dom/vr/test/reftest/reftest.list
@@ -1,10 +1,10 @@
 # WebVR Reftests
 # Please confirm there is no other VR display connected. Otherwise, VRPuppetDisplay can't be attached.
 default-preferences pref(dom.vr.puppet.enabled,true) pref(dom.vr.test.enabled,true) pref(dom.vr.require-gesture,false) pref(dom.vr.puppet.submitframe,1) pref(dom.vr.display.rafMaxDuration,200) pref(dom.vr.display.enumerate.interval,0) pref(dom.vr.controller.enumerate.interval,0)
-
+# WebVR Tests have been disabled as refactoring of gfxVRPuppet is landing.  Dependencies for re-enabling these are tracked by meta bug 1555185.
 # VR SubmitFrame is only implemented for D3D11.1 and MacOSX now.
 # Our Windows 7 test machines don't support D3D11.1, so we run these tests on Windows 8+ only.
-skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == draw_rect.html wrapper.html?draw_rect.png
+# skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == draw_rect.html wrapper.html?draw_rect.png
 # On MacOSX platform, getting different color interpolation result.
 # For lower resolution Mac hardware, we need to adjust it to fuzzy-if(cocoaWidget,0-1,0-1200).
-fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||cocoaWidget,0-1,0-600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == change_size.html wrapper.html?change_size.png
+# fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)||cocoaWidget,0-1,0-600) skip-if((!winWidget&&release_or_beta)||Android||gtkWidget||!layersGPUAccelerated||/^Windows\x20NT\x206\.1/.test(http.oscpu)) == change_size.html wrapper.html?change_size.png
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -219,17 +219,17 @@ partial interface Navigator {
   [ChromeOnly, Pref="dom.vr.enabled"]
   readonly attribute boolean isWebVRContentDetected;
   [ChromeOnly, Pref="dom.vr.enabled"]
   readonly attribute boolean isWebVRContentPresenting;
   [ChromeOnly, Pref="dom.vr.enabled"]
   void requestVRPresentation(VRDisplay display);
 };
 partial interface Navigator {
-  [Pref="dom.vr.test.enabled"]
+  [Pref="dom.vr.puppet.enabled"]
   VRServiceTest requestVRServiceTest();
 };
 
 // http://webaudio.github.io/web-midi-api/#requestmidiaccess
 partial interface Navigator {
   [Throws, Pref="dom.webmidi.enabled"]
   Promise<MIDIAccess> requestMIDIAccess(optional MIDIOptions options);
 };
--- a/dom/webidl/VRDisplay.webidl
+++ b/dom/webidl/VRDisplay.webidl
@@ -144,24 +144,16 @@ interface VRFrameData {
   [Throws, Pure] readonly attribute Float32Array leftViewMatrix;
 
   [Throws, Pure] readonly attribute Float32Array rightProjectionMatrix;
   [Throws, Pure] readonly attribute Float32Array rightViewMatrix;
 
   [Pure] readonly attribute VRPose pose;
 };
 
-[Constructor,
- Pref="dom.vr.test.enabled",
- HeaderFile="mozilla/dom/VRDisplay.h"]
-interface VRSubmitFrameResult {
-  readonly attribute unsigned long frameNum;
-  readonly attribute DOMString? base64Image;
-};
-
 [Pref="dom.vr.enabled",
  HeaderFile="mozilla/dom/VRDisplay.h"]
 interface VREyeParameters {
   /**
    * offset is a 3-component vector representing an offset to
    * translate the eye. This value may vary from frame
    * to frame if the user adjusts their headset ipd.
    */
@@ -243,19 +235,16 @@ interface VRDisplay : EventTarget {
    * MUST return a VRPose with the same values until the next call to
    * submitFrame().
    *
    * The VRPose will contain the position, orientation, velocity,
    * and acceleration of each of these properties.
    */
   [NewObject] VRPose getPose();
 
-  [Pref="dom.vr.test.enabled"]
-  boolean getSubmitFrameResult(VRSubmitFrameResult result);
-
   /**
    * Reset the pose for this display, treating its current position and
    * orientation as the "origin/zero" values. VRPose.position,
    * VRPose.orientation, and VRStageParameters.sittingToStandingTransform may be
    * updated when calling resetPose(). This should be called in only
    * sitting-space experiences.
    */
   void resetPose();
--- a/dom/webidl/VRServiceTest.webidl
+++ b/dom/webidl/VRServiceTest.webidl
@@ -1,39 +1,81 @@
 /* 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",
+[Pref="dom.vr.puppet.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 setMountState(boolean isMounted);
-  void update();
+  void create();
+  attribute boolean capPosition;
+  attribute boolean capOrientation;
+  attribute boolean capPresent;
+  attribute boolean capExternal;
+  attribute boolean capAngularAcceleration;
+  attribute boolean capLinearAcceleration;
+  attribute boolean capStageParameters;
+  attribute boolean capMountDetection;
+  attribute boolean capPositionEmulated;
+  void setEyeFOV(VREye eye,
+                 double upDegree, double rightDegree,
+                 double downDegree, double leftDegree);
+  void setEyeOffset(VREye eye, double offsetX,
+                    double offsetY, double offsetZ);
+  void setEyeResolution(unsigned long renderWidth,
+                        unsigned long renderHeight);
+  void setConnected(boolean connected);
+  void setMounted(boolean mounted);
+  void setStageSize(double width, double height);
+  [Throws] void setSittingToStandingTransform(Float32Array sittingToStandingTransform);
+  [Throws] void setPose(Float32Array? position, Float32Array? linearVelocity,
+                        Float32Array? linearAcceleration, Float32Array? orientation,
+                        Float32Array? angularVelocity, Float32Array? angularAcceleration);
 };
 
-[Pref="dom.vr.test.enabled",
+[Pref="dom.vr.puppet.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);
+  void create();
+  void clear();
+  attribute GamepadHand hand;
+  attribute boolean capPosition;
+  attribute boolean capOrientation;
+  attribute boolean capAngularAcceleration;
+  attribute boolean capLinearAcceleration;
+  attribute unsigned long axisCount;
+  attribute unsigned long buttonCount;
+  attribute unsigned long hapticCount;
+  [Throws] void setPose(Float32Array? position, Float32Array? linearVelocity,
+                        Float32Array? linearAcceleration, Float32Array? orientation,
+                        Float32Array? angularVelocity, Float32Array? angularAcceleration);
+  void setButtonPressed(unsigned long buttonIdx, boolean pressed);
+  void setButtonTouched(unsigned long buttonIdx, boolean touched);
+  void setButtonTrigger(unsigned long buttonIdx, double trigger);
+  void setAxisValue(unsigned long axisIdx, double value);
 };
 
-[Pref="dom.vr.test.enabled",
+[Pref="dom.vr.puppet.enabled",
  HeaderFile="mozilla/dom/VRServiceTest.h"]
 interface VRServiceTest {
-  [Throws, NewObject]
-  Promise<VRMockDisplay> attachVRDisplay(DOMString id);
-  [Throws, NewObject]
-  Promise<VRMockController> attachVRController(DOMString id);
+  VRMockDisplay getVRDisplay();
+  [Throws] VRMockController getVRController(unsigned long controllerIdx);
+  [Throws] Promise<void> run();
+  [Throws] Promise<void> reset();
+  void commit();
+  void end();
+  void clearAll();
+  void timeout(unsigned long duration);
+  void wait(unsigned long duration);
+  void waitSubmit();
+  void waitPresentationStart();
+  void waitPresentationEnd();
+  [Throws]
+  void waitHapticIntensity(unsigned long controllerIdx, unsigned long hapticIdx, double intensity);
+  void captureFrame();
+  void acknowledgeFrame();
+  void rejectFrame();
+  void startTimer();
+  void stopTimer();
 };
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -54,21 +54,16 @@ already_AddRefed<VRDisplayPresentation> 
   ++mPresentationCount;
   RefPtr<VRDisplayPresentation> presentation =
       new VRDisplayPresentation(this, aLayers, aGroup);
   return presentation.forget();
 }
 
 void VRDisplayClient::PresentationDestroyed() { --mPresentationCount; }
 
-void VRDisplayClient::ZeroSensor() {
-  VRManagerChild* vm = VRManagerChild::Get();
-  vm->SendResetSensor(mDisplayInfo.mDisplayID);
-}
-
 void VRDisplayClient::SetGroupMask(uint32_t aGroupMask) {
   VRManagerChild* vm = VRManagerChild::Get();
   vm->SendSetGroupMask(mDisplayInfo.mDisplayID, aGroupMask);
 }
 
 bool VRDisplayClient::IsPresentationGenerationCurrent() const {
   if (mLastPresentingGeneration !=
       mDisplayInfo.mDisplayState.presentingGeneration) {
--- a/gfx/vr/VRDisplayClient.h
+++ b/gfx/vr/VRDisplayClient.h
@@ -28,18 +28,16 @@ class VRDisplayClient {
 
   MOZ_CAN_RUN_SCRIPT void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo);
   void UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult);
 
   const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
   virtual const VRHMDSensorState& GetSensorState() const;
   void GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult);
 
-  virtual void ZeroSensor();
-
   already_AddRefed<VRDisplayPresentation> BeginPresentation(
       const nsTArray<dom::VRLayer>& aLayers, uint32_t aGroup);
   void PresentationDestroyed();
 
   bool GetIsConnected() const;
 
   void NotifyDisconnected();
   void SetGroupMask(uint32_t aGroupMask);
deleted file mode 100644
--- a/gfx/vr/VRDisplayHost.cpp
+++ /dev/null
@@ -1,454 +0,0 @@
-/* -*- 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 "VRDisplayHost.h"
-#include "VRThread.h"
-#include "gfxVR.h"
-#include "ipc/VRLayerParent.h"
-#include "mozilla/StaticPrefs.h"
-#include "mozilla/dom/GamepadBinding.h"       // For GamepadMappingType
-#include "mozilla/layers/CompositorThread.h"  // for CompositorThreadHolder
-#include "mozilla/layers/TextureHost.h"
-
-#if defined(XP_WIN)
-
-#  include <d3d11.h>
-#  include "gfxWindowsPlatform.h"
-#  include "../layers/d3d11/CompositorD3D11.h"
-#  include "mozilla/gfx/DeviceManagerDx.h"
-#  include "mozilla/layers/TextureD3D11.h"
-
-#elif defined(XP_MACOSX)
-
-#  include "mozilla/gfx/MacIOSurface.h"
-
-#endif
-
-#if defined(MOZ_WIDGET_ANDROID)
-#  include "mozilla/layers/CompositorThread.h"
-// Max frame duration on Android before the watchdog submits a new one.
-// Probably we can get rid of this when we enforce that SubmitFrame can only be
-// called in a VRDisplay loop.
-#  define ANDROID_MAX_FRAME_DURATION 4000
-#endif  // defined(MOZ_WIDGET_ANDROID)
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::layers;
-
-VRDisplayHost::AutoRestoreRenderState::AutoRestoreRenderState(
-    VRDisplayHost* aDisplay)
-    : mDisplay(aDisplay), mSuccess(true) {
-#if defined(XP_WIN)
-  ID3D11DeviceContext1* context = mDisplay->GetD3DDeviceContext();
-  ID3DDeviceContextState* state = mDisplay->GetD3DDeviceContextState();
-  if (!context || !state) {
-    mSuccess = false;
-    return;
-  }
-  context->SwapDeviceContextState(state,
-                                  getter_AddRefs(mPrevDeviceContextState));
-#endif
-}
-
-VRDisplayHost::AutoRestoreRenderState::~AutoRestoreRenderState() {
-#if defined(XP_WIN)
-  ID3D11DeviceContext1* context = mDisplay->GetD3DDeviceContext();
-  if (context && mSuccess) {
-    context->SwapDeviceContextState(mPrevDeviceContextState, nullptr);
-  }
-#endif
-}
-
-bool VRDisplayHost::AutoRestoreRenderState::IsSuccess() { return mSuccess; }
-
-VRDisplayHost::VRDisplayHost(VRDeviceType aType)
-    : mDisplayInfo{},
-      mLastUpdateDisplayInfo{},
-      mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"),
-      mCurrentSubmitTask(nullptr),
-      mFrameStarted(false) {
-  MOZ_COUNT_CTOR(VRDisplayHost);
-  mDisplayInfo.mType = aType;
-  mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
-  mDisplayInfo.mPresentingGroups = 0;
-  mDisplayInfo.mGroupMask = kVRGroupContent;
-  mDisplayInfo.mFrameId = 0;
-  mDisplayInfo.mDisplayState.presentingGeneration = 0;
-  mDisplayInfo.mDisplayState.displayName[0] = '\0';
-
-#if defined(MOZ_WIDGET_ANDROID)
-  mLastSubmittedFrameId = 0;
-  mLastStartedFrame = 0;
-#endif  // defined(MOZ_WIDGET_ANDROID)
-}
-
-VRDisplayHost::~VRDisplayHost() {
-  CancelCurrentSubmitTask();
-
-  if (mSubmitThread) {
-    mSubmitThread->Shutdown();
-    mSubmitThread = nullptr;
-  }
-  MOZ_COUNT_DTOR(VRDisplayHost);
-}
-
-void VRDisplayHost::ShutdownSubmitThread() {
-  if (mSubmitThread) {
-    mSubmitThread->Shutdown();
-    mSubmitThread = nullptr;
-  }
-}
-
-#if defined(XP_WIN)
-bool VRDisplayHost::CreateD3DObjects() {
-  if (!mDevice) {
-    RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
-    if (!device) {
-      NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11Device");
-      return false;
-    }
-    if (FAILED(device->QueryInterface(__uuidof(ID3D11Device1),
-                                      getter_AddRefs(mDevice)))) {
-      NS_WARNING(
-          "VRDisplayHost::CreateD3DObjects failed to get a D3D11Device1");
-      return false;
-    }
-  }
-  if (!mContext) {
-    mDevice->GetImmediateContext1(getter_AddRefs(mContext));
-    if (!mContext) {
-      NS_WARNING(
-          "VRDisplayHost::CreateD3DObjects failed to get an immediate context");
-      return false;
-    }
-  }
-  if (!mDeviceContextState) {
-    D3D_FEATURE_LEVEL featureLevels[]{D3D_FEATURE_LEVEL_11_1,
-                                      D3D_FEATURE_LEVEL_11_0};
-    mDevice->CreateDeviceContextState(0, featureLevels, 2, D3D11_SDK_VERSION,
-                                      __uuidof(ID3D11Device1), nullptr,
-                                      getter_AddRefs(mDeviceContextState));
-  }
-  if (!mDeviceContextState) {
-    NS_WARNING(
-        "VRDisplayHost::CreateD3DObjects failed to get a "
-        "D3D11DeviceContextState");
-    return false;
-  }
-  return true;
-}
-
-ID3D11Device1* VRDisplayHost::GetD3DDevice() { return mDevice; }
-
-ID3D11DeviceContext1* VRDisplayHost::GetD3DDeviceContext() { return mContext; }
-
-ID3DDeviceContextState* VRDisplayHost::GetD3DDeviceContextState() {
-  return mDeviceContextState;
-}
-
-#endif  // defined(XP_WIN)
-
-void VRDisplayHost::SetGroupMask(uint32_t aGroupMask) {
-  mDisplayInfo.mGroupMask = aGroupMask;
-}
-
-bool VRDisplayHost::GetIsConnected() {
-  return mDisplayInfo.mDisplayState.isConnected;
-}
-
-void VRDisplayHost::AddLayer(VRLayerParent* aLayer) {
-  mLayers.AppendElement(aLayer);
-  mDisplayInfo.mPresentingGroups |= aLayer->GetGroup();
-  if (mLayers.Length() == 1) {
-    StartPresentation();
-  }
-
-  // Ensure that the content process receives the change immediately
-  VRManager* vm = VRManager::Get();
-  vm->RefreshVRDisplays();
-}
-
-void VRDisplayHost::RemoveLayer(VRLayerParent* aLayer) {
-  mLayers.RemoveElement(aLayer);
-  if (mLayers.Length() == 0) {
-    StopPresentation();
-  }
-  mDisplayInfo.mPresentingGroups = 0;
-  for (auto layer : mLayers) {
-    mDisplayInfo.mPresentingGroups |= layer->GetGroup();
-  }
-
-  // Ensure that the content process receives the change immediately
-  VRManager* vm = VRManager::Get();
-  vm->RefreshVRDisplays();
-}
-
-void VRDisplayHost::StartFrame() {
-  AUTO_PROFILER_TRACING("VR", "GetSensorState", OTHER);
-
-  TimeStamp now = TimeStamp::Now();
-#if defined(MOZ_WIDGET_ANDROID)
-  const TimeStamp lastFrameStart =
-      mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
-  const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
-  double duration =
-      lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
-  /**
-   * Do not start more VR frames until the last submitted frame is already
-   * processed.
-   */
-  if (isPresenting && mLastStartedFrame > 0 &&
-      mDisplayInfo.mDisplayState.lastSubmittedFrameId < mLastStartedFrame &&
-      duration < (double)ANDROID_MAX_FRAME_DURATION) {
-    return;
-  }
-#endif  // !defined(MOZ_WIDGET_ANDROID)
-
-  ++mDisplayInfo.mFrameId;
-  size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
-  mDisplayInfo.mLastSensorState[bufferIndex] = GetSensorState();
-  mLastFrameStart[bufferIndex] = now;
-  mFrameStarted = true;
-#if defined(MOZ_WIDGET_ANDROID)
-  mLastStartedFrame = mDisplayInfo.mFrameId;
-#endif  // !defined(MOZ_WIDGET_ANDROID)
-}
-
-void VRDisplayHost::NotifyVSync() {
-  /**
-   * If this display isn't presenting, refresh the sensors and trigger
-   * VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
-   */
-  if (mDisplayInfo.mPresentingGroups == 0) {
-    VRManager* vm = VRManager::Get();
-    MOZ_ASSERT(vm);
-    vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
-  }
-}
-
-void VRDisplayHost::CheckWatchDog() {
-  /**
-   * We will trigger a new frame immediately after a successful frame texture
-   * submission.  If content fails to call VRDisplay.submitFrame after
-   * dom.vr.display.rafMaxDuration milliseconds has elapsed since the last
-   * VRDisplay.requestAnimationFrame, we act as a "watchdog" and kick-off
-   * a new VRDisplay.requestAnimationFrame to avoid a render loop stall and
-   * to give content a chance to recover.
-   *
-   * If the lower level VR platform API's are rejecting submitted frames,
-   * such as when the Oculus "Health and Safety Warning" is displayed,
-   * we will not kick off the next frame immediately after VRDisplay.submitFrame
-   * as it would result in an unthrottled render loop that would free run at
-   * potentially extreme frame rates.  To ensure that content has a chance to
-   * resume its presentation when the frames are accepted once again, we rely
-   * on this "watchdog" to act as a VR refresh driver cycling at a rate defined
-   * by dom.vr.display.rafMaxDuration.
-   *
-   * This number must be larger than the slowest expected frame time during
-   * normal VR presentation, but small enough not to break content that
-   * makes assumptions of reasonably minimal VSync rate.
-   *
-   * The slowest expected refresh rate for a VR display currently is an
-   * Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz.
-   * A dom.vr.display.rafMaxDuration value of 50 milliseconds results in a 20hz
-   * rate, which avoids inadvertent triggering of the watchdog during
-   * Oculus ASW even if every second frame is dropped.
-   */
-  bool bShouldStartFrame = false;
-
-  // If content fails to call VRDisplay.submitFrame, we must eventually
-  // time-out and trigger a new frame.
-  TimeStamp lastFrameStart =
-      mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
-  if (lastFrameStart.IsNull()) {
-    bShouldStartFrame = true;
-  } else {
-    TimeDuration duration = TimeStamp::Now() - lastFrameStart;
-    if (duration.ToMilliseconds() >
-        StaticPrefs::dom_vr_display_rafMaxDuration()) {
-      bShouldStartFrame = true;
-    }
-  }
-
-  if (bShouldStartFrame) {
-    VRManager* vm = VRManager::Get();
-    MOZ_ASSERT(vm);
-    vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
-  }
-}
-
-void VRDisplayHost::Run1msTasks(double aDeltaTime) {
-  // To override in children
-}
-
-void VRDisplayHost::Run10msTasks() { CheckWatchDog(); }
-
-void VRDisplayHost::Run100msTasks() {
-  // to override in children
-}
-
-void VRDisplayHost::SubmitFrameInternal(
-    const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
-    const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) {
-#if !defined(MOZ_WIDGET_ANDROID)
-  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
-#endif  // !defined(MOZ_WIDGET_ANDROID)
-  AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplayHost", OTHER);
-
-  {  // scope lock
-    MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
-
-    if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) {
-      mCurrentSubmitTask = nullptr;
-      return;
-    }
-    mCurrentSubmitTask = nullptr;
-  }
-
-#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
-
-  /**
-   * Trigger the next VSync immediately after we are successfully
-   * submitting frames.  As SubmitFrame is responsible for throttling
-   * the render loop, if we don't successfully call it, we shouldn't trigger
-   * NotifyVRVsync immediately, as it will run unbounded.
-   * If NotifyVRVsync is not called here due to SubmitFrame failing, the
-   * fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause
-   * frames to continue at a lower refresh rate until frame submission
-   * succeeds again.
-   */
-  VRManager* vm = VRManager::Get();
-  MessageLoop* loop = CompositorThreadHolder::Loop();
-
-  loop->PostTask(NewRunnableMethod<const uint32_t>(
-      "gfx::VRManager::NotifyVRVsync", vm, &VRManager::NotifyVRVsync,
-      mDisplayInfo.mDisplayID));
-#endif
-}
-
-void VRDisplayHost::SubmitFrame(VRLayerParent* aLayer,
-                                const layers::SurfaceDescriptor& aTexture,
-                                uint64_t aFrameId,
-                                const gfx::Rect& aLeftEyeRect,
-                                const gfx::Rect& aRightEyeRect) {
-  MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
-  if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
-    // Suppress layers hidden by the group mask
-    return;
-  }
-
-  // Ensure that we only accept the first SubmitFrame call per RAF cycle.
-  if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
-    return;
-  }
-
-#if defined(MOZ_WIDGET_ANDROID)
-  /**
-   * Do not queue more submit frames until the last submitted frame is already
-   * processed and the new WebGL texture is ready.
-   */
-  if (mLastSubmittedFrameId > 0 &&
-      mLastSubmittedFrameId !=
-          mDisplayInfo.mDisplayState.lastSubmittedFrameId) {
-    mLastStartedFrame = 0;
-    return;
-  }
-
-  mLastSubmittedFrameId = aFrameId;
-#endif  // !defined(MOZ_WIDGET_ANDROID)
-
-  mFrameStarted = false;
-
-  RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<
-      StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t,
-      StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>(
-      "gfx::VRDisplayHost::SubmitFrameInternal", this,
-      &VRDisplayHost::SubmitFrameInternal, aTexture, aFrameId, aLeftEyeRect,
-      aRightEyeRect);
-
-  if (!mCurrentSubmitTask) {
-    mCurrentSubmitTask = task;
-#if !defined(MOZ_WIDGET_ANDROID)
-    if (!mSubmitThread) {
-      mSubmitThread = new VRThread(NS_LITERAL_CSTRING("VR_SubmitFrame"));
-    }
-    mSubmitThread->Start();
-    mSubmitThread->PostTask(task.forget());
-#else
-    CompositorThreadHolder::Loop()->PostTask(task.forget());
-#endif  // defined(MOZ_WIDGET_ANDROID)
-  }
-}
-
-void VRDisplayHost::CancelCurrentSubmitTask() {
-  MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
-  if (mCurrentSubmitTask) {
-    mCurrentSubmitTask->Cancel();
-    mCurrentSubmitTask = nullptr;
-  }
-}
-
-bool VRDisplayHost::CheckClearDisplayInfoDirty() {
-  if (mDisplayInfo == mLastUpdateDisplayInfo) {
-    return false;
-  }
-  mLastUpdateDisplayInfo = mDisplayInfo;
-  return true;
-}
-
-void VRDisplayHost::StartVRNavigation() {}
-
-void VRDisplayHost::StopVRNavigation(const TimeDuration& aTimeout) {}
-
-VRControllerHost::VRControllerHost(VRDeviceType aType, dom::GamepadHand aHand,
-                                   uint32_t aDisplayID)
-    : mControllerInfo{}, mVibrateIndex(0) {
-  MOZ_COUNT_CTOR(VRControllerHost);
-  mControllerInfo.mType = aType;
-  mControllerInfo.mControllerState.hand = aHand;
-  mControllerInfo.mMappingType = dom::GamepadMappingType::_empty;
-  mControllerInfo.mDisplayID = aDisplayID;
-  mControllerInfo.mControllerID = VRSystemManager::AllocateControllerID();
-}
-
-VRControllerHost::~VRControllerHost() { MOZ_COUNT_DTOR(VRControllerHost); }
-
-const VRControllerInfo& VRControllerHost::GetControllerInfo() const {
-  return mControllerInfo;
-}
-
-void VRControllerHost::SetButtonPressed(uint64_t aBit) {
-  mControllerInfo.mControllerState.buttonPressed = aBit;
-}
-
-uint64_t VRControllerHost::GetButtonPressed() {
-  return mControllerInfo.mControllerState.buttonPressed;
-}
-
-void VRControllerHost::SetButtonTouched(uint64_t aBit) {
-  mControllerInfo.mControllerState.buttonTouched = aBit;
-}
-
-uint64_t VRControllerHost::GetButtonTouched() {
-  return mControllerInfo.mControllerState.buttonTouched;
-}
-
-void VRControllerHost::SetPose(const dom::GamepadPoseState& aPose) {
-  mPose = aPose;
-}
-
-const dom::GamepadPoseState& VRControllerHost::GetPose() { return mPose; }
-
-dom::GamepadHand VRControllerHost::GetHand() {
-  return mControllerInfo.mControllerState.hand;
-}
-
-void VRControllerHost::SetVibrateIndex(uint64_t aIndex) {
-  mVibrateIndex = aIndex;
-}
-
-uint64_t VRControllerHost::GetVibrateIndex() { return mVibrateIndex; }
deleted file mode 100644
--- a/gfx/vr/VRDisplayHost.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/* -*- 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 GFX_VR_DISPLAY_HOST_H
-#define GFX_VR_DISPLAY_HOST_H
-
-#include "gfxVR.h"
-#include "nsTArray.h"
-#include "nsString.h"
-#include "nsCOMPtr.h"
-#include "mozilla/RefPtr.h"
-#include "mozilla/gfx/2D.h"
-#include "mozilla/Atomics.h"
-#include "mozilla/EnumeratedArray.h"
-#include "mozilla/TimeStamp.h"
-#include "mozilla/TypedEnumBits.h"
-#include "mozilla/dom/GamepadPoseState.h"
-#include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
-
-#if defined(XP_WIN)
-#  include <d3d11_1.h>
-#elif defined(XP_MACOSX)
-class MacIOSurface;
-#endif
-namespace mozilla {
-namespace gfx {
-class VRThread;
-class VRLayerParent;
-
-class VRDisplayHost {
- public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayHost)
-
-  const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
-
-  void AddLayer(VRLayerParent* aLayer);
-  void RemoveLayer(VRLayerParent* aLayer);
-
-  virtual void ZeroSensor() = 0;
-  virtual void StartPresentation() = 0;
-  virtual void StopPresentation() = 0;
-  virtual void StartVRNavigation();
-  virtual void StopVRNavigation(const TimeDuration& aTimeout);
-  void NotifyVSync();
-  virtual void Run1msTasks(double aDeltaTime);
-  virtual void Run10msTasks();
-  virtual void Run100msTasks();
-
-  void StartFrame();
-  void SubmitFrame(VRLayerParent* aLayer,
-                   const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
-                   const gfx::Rect& aLeftEyeRect,
-                   const gfx::Rect& aRightEyeRect);
-  void CancelCurrentSubmitTask();
-  bool CheckClearDisplayInfoDirty();
-  void SetGroupMask(uint32_t aGroupMask);
-  bool GetIsConnected();
-  void ShutdownSubmitThread();
-
-  class AutoRestoreRenderState {
-   public:
-    explicit AutoRestoreRenderState(VRDisplayHost* aDisplay);
-    ~AutoRestoreRenderState();
-    bool IsSuccess();
-
-   private:
-    RefPtr<VRDisplayHost> mDisplay;
-#if defined(XP_WIN)
-    RefPtr<ID3DDeviceContextState> mPrevDeviceContextState;
-#endif
-    bool mSuccess;
-  };
-
- protected:
-  explicit VRDisplayHost(VRDeviceType aType);
-  virtual ~VRDisplayHost();
-
-  // This SubmitFrame() must be overridden by children and block until
-  // the next frame is ready to start and the resources in aTexture can
-  // safely be released.
-  virtual bool SubmitFrame(const layers::SurfaceDescriptor& aTexture,
-                           uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect) = 0;
-
-  VRDisplayInfo mDisplayInfo;
-  TimeStamp mLastFrameStart[kVRMaxLatencyFrames];
-
-  nsTArray<VRLayerParent*> mLayers;
-  // Weak reference to mLayers entries are cleared in
-  // VRLayerParent destructor
-
- protected:
-  virtual VRHMDSensorState& GetSensorState() = 0;
-
-  RefPtr<VRThread> mSubmitThread;
-
- private:
-  void SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
-                           uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect);
-  void CheckWatchDog();
-
-  VRDisplayInfo mLastUpdateDisplayInfo;
-
-  mozilla::Monitor mCurrentSubmitTaskMonitor;
-  RefPtr<CancelableRunnable> mCurrentSubmitTask;
-  bool mFrameStarted;
-#if defined(MOZ_WIDGET_ANDROID)
- protected:
-  uint64_t mLastSubmittedFrameId;
-  uint64_t mLastStartedFrame;
-#endif  // defined(MOZ_WIDGET_ANDROID)
-
-#if defined(XP_WIN)
- protected:
-  bool CreateD3DObjects();
-  RefPtr<ID3D11Device1> mDevice;
-  RefPtr<ID3D11DeviceContext1> mContext;
-  ID3D11Device1* GetD3DDevice();
-  ID3D11DeviceContext1* GetD3DDeviceContext();
-  ID3DDeviceContextState* GetD3DDeviceContextState();
-
- private:
-  RefPtr<ID3DDeviceContextState> mDeviceContextState;
-#endif
-};
-
-class VRControllerHost {
- public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerHost)
-
-  const VRControllerInfo& GetControllerInfo() const;
-  void SetButtonPressed(uint64_t aBit);
-  uint64_t GetButtonPressed();
-  void SetButtonTouched(uint64_t aBit);
-  uint64_t GetButtonTouched();
-  void SetPose(const dom::GamepadPoseState& aPose);
-  const dom::GamepadPoseState& GetPose();
-  dom::GamepadHand GetHand();
-  void SetVibrateIndex(uint64_t aIndex);
-  uint64_t GetVibrateIndex();
-
- protected:
-  explicit VRControllerHost(VRDeviceType aType, dom::GamepadHand aHand,
-                            uint32_t aDisplayID);
-  virtual ~VRControllerHost();
-
-  VRControllerInfo mControllerInfo;
-  uint64_t mVibrateIndex;
-  dom::GamepadPoseState mPose;
-};
-
-}  // namespace gfx
-}  // namespace mozilla
-
-#endif /* GFX_VR_DISPLAY_HOST_H */
deleted file mode 100644
--- a/gfx/vr/VRDisplayLocal.cpp
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- 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 "VRDisplayLocal.h"
-#include "VRThread.h"
-
-#include "gfxVR.h"
-#include "ipc/VRLayerParent.h"
-#include "mozilla/dom/GamepadBinding.h"  // For GamepadMappingType
-#include "mozilla/layers/TextureHost.h"
-
-#if defined(XP_WIN)
-
-#  include <d3d11.h>
-#  include "../layers/d3d11/CompositorD3D11.h"
-#  include "gfxWindowsPlatform.h"
-#  include "mozilla/gfx/DeviceManagerDx.h"
-#  include "mozilla/layers/TextureD3D11.h"
-
-#elif defined(XP_MACOSX)
-
-#  include "mozilla/gfx/MacIOSurface.h"
-
-#endif
-
-#if defined(MOZ_WIDGET_ANDROID)
-#  include "mozilla/layers/CompositorThread.h"
-#endif  // defined(MOZ_WIDGET_ANDROID)
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::layers;
-
-VRDisplayLocal::VRDisplayLocal(VRDeviceType aType) : VRDisplayHost(aType) {
-  MOZ_COUNT_CTOR_INHERITED(VRDisplayLocal, VRDisplayHost);
-}
-
-VRDisplayLocal::~VRDisplayLocal() {
-  MOZ_COUNT_DTOR_INHERITED(VRDisplayLocal, VRDisplayHost);
-}
-
-bool VRDisplayLocal::SubmitFrame(const layers::SurfaceDescriptor& aTexture,
-                                 uint64_t aFrameId,
-                                 const gfx::Rect& aLeftEyeRect,
-                                 const gfx::Rect& aRightEyeRect) {
-#if !defined(MOZ_WIDGET_ANDROID)
-  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
-#endif  // !defined(MOZ_WIDGET_ANDROID)
-
-  switch (aTexture.type()) {
-#if defined(XP_WIN)
-    case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
-      if (!CreateD3DObjects()) {
-        return false;
-      }
-      const SurfaceDescriptorD3D10& surf =
-          aTexture.get_SurfaceDescriptorD3D10();
-      RefPtr<ID3D11Texture2D> dxTexture;
-      HRESULT hr = mDevice->OpenSharedResource(
-          (HANDLE)surf.handle(), __uuidof(ID3D11Texture2D),
-          (void**)(ID3D11Texture2D**)getter_AddRefs(dxTexture));
-      if (FAILED(hr) || !dxTexture) {
-        NS_WARNING("Failed to open shared texture");
-        return false;
-      }
-
-      // Similar to LockD3DTexture in TextureD3D11.cpp
-      RefPtr<IDXGIKeyedMutex> mutex;
-      dxTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
-      if (mutex) {
-        HRESULT hr = mutex->AcquireSync(0, 1000);
-        if (hr == WAIT_TIMEOUT) {
-          gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout";
-        } else if (hr == WAIT_ABANDONED) {
-          gfxCriticalNote << "GFX: D3D11 lock mutex abandoned";
-        }
-        if (FAILED(hr)) {
-          NS_WARNING("Failed to lock the texture");
-          return false;
-        }
-      }
-      bool success =
-          SubmitFrame(dxTexture, surf.size(), aLeftEyeRect, aRightEyeRect);
-      if (mutex) {
-        HRESULT hr = mutex->ReleaseSync(0);
-        if (FAILED(hr)) {
-          NS_WARNING("Failed to unlock the texture");
-        }
-      }
-      return success;
-    }
-#elif defined(XP_MACOSX)
-    case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
-      const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface();
-      RefPtr<MacIOSurface> surf =
-          MacIOSurface::LookupSurface(desc.surfaceId(), desc.scaleFactor(),
-                                      !desc.isOpaque(), desc.yUVColorSpace());
-      if (!surf) {
-        NS_WARNING("VRDisplayHost::SubmitFrame failed to get a MacIOSurface");
-        return false;
-      }
-      IntSize texSize = gfx::IntSize(surf->GetDevicePixelWidth(),
-                                     surf->GetDevicePixelHeight());
-      if (!SubmitFrame(surf, texSize, aLeftEyeRect, aRightEyeRect)) {
-        return false;
-      }
-      return true;
-    }
-#elif defined(MOZ_WIDGET_ANDROID)
-    case SurfaceDescriptor::TSurfaceTextureDescriptor: {
-      const SurfaceTextureDescriptor& desc =
-          aTexture.get_SurfaceTextureDescriptor();
-      if (!SubmitFrame(desc, aLeftEyeRect, aRightEyeRect)) {
-        return false;
-      }
-      return true;
-    }
-#endif
-    default: {
-      NS_WARNING("Unsupported SurfaceDescriptor type for VR layer texture");
-      return false;
-    }
-  }
-}
deleted file mode 100644
--- a/gfx/vr/VRDisplayLocal.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* -*- 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 GFX_VR_DISPLAY_LOCAL_H
-#define GFX_VR_DISPLAY_LOCAL_H
-
-#include "gfxVR.h"
-#include "VRDisplayHost.h"
-
-#if defined(XP_WIN)
-#  include <d3d11_1.h>
-#elif defined(XP_MACOSX)
-class MacIOSurface;
-#endif
-
-namespace mozilla {
-namespace gfx {
-class VRThread;
-
-class VRDisplayLocal : public VRDisplayHost {
- public:
-#if defined(XP_WIN)
-  // Subclasses should override this SubmitFrame function.
-  // Returns true if the SubmitFrame call will block as necessary
-  // to control timing of the next frame and throttle the render loop
-  // for the needed framerate.
-  virtual bool SubmitFrame(ID3D11Texture2D* aSource, const IntSize& aSize,
-                           const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect) = 0;
-#elif defined(XP_MACOSX)
-  virtual bool SubmitFrame(MacIOSurface* aMacIOSurface, const IntSize& aSize,
-                           const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect) = 0;
-#elif defined(MOZ_WIDGET_ANDROID)
-  virtual bool SubmitFrame(
-      const mozilla::layers::SurfaceTextureDescriptor& aSurface,
-      const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) = 0;
-#endif
-
- protected:
-  explicit VRDisplayLocal(VRDeviceType aType);
-  virtual ~VRDisplayLocal();
-
- private:
-  bool SubmitFrame(const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
-                   const gfx::Rect& aLeftEyeRect,
-                   const gfx::Rect& aRightEyeRect) final;
-};
-
-}  // namespace gfx
-}  // namespace mozilla
-
-#endif  // GFX_VR_DISPLAY_LOCAL_H
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -1,178 +1,367 @@
 /* -*- 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 "VRManager.h"
+
 #include "VRManagerParent.h"
 #include "VRThread.h"
 #include "gfxVR.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/VRDisplay.h"
 #include "mozilla/dom/GamepadEventTypes.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/StaticPrefs.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 
 #include "gfxVR.h"
-#include "gfxVRExternal.h"
+#include "gfxVRMutex.h"
+#include <cstring>
 
-#include "gfxVRPuppet.h"
 #include "ipc/VRLayerParent.h"
 #if !defined(MOZ_WIDGET_ANDROID)
-#  include "service/VRService.h"
-#  include "service/VRServiceManager.h"
+#  include "VRServiceHost.h"
 #endif
 
+#ifdef XP_WIN
+#  include "CompositorD3D11.h"
+#  include "TextureD3D11.h"
+#  include <d3d11.h>
+#  include "gfxWindowsPlatform.h"
+#  include "mozilla/gfx/DeviceManagerDx.h"
+static const char* kShmemName = "moz.gecko.vr_ext.0.0.1";
+#elif defined(XP_MACOSX)
+#  include "mozilla/gfx/MacIOSurface.h"
+#  include <sys/mman.h>
+#  include <sys/stat.h> /* For mode constants */
+#  include <fcntl.h>    /* For O_* constants */
+#  include <errno.h>
+static const char* kShmemName = "/moz.gecko.vr_ext.0.0.1";
+#elif defined(MOZ_WIDGET_ANDROID)
+#  include <string.h>
+#  include <pthread.h>
+#  include "GeckoVRManager.h"
+#  include "mozilla/layers/CompositorThread.h"
+#endif  // defined(MOZ_WIDGET_ANDROID)
+
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
+#if !defined(MOZ_WIDGET_ANDROID)
+namespace {
+void YieldThread() {
+#  if defined(XP_WIN)
+  ::Sleep(0);
+#  else
+  ::sleep(0);
+#  endif
+}
+}  // anonymous namespace
+#endif  // !defined(MOZ_WIDGET_ANDROID)
+
 namespace mozilla {
 namespace gfx {
 
-static StaticRefPtr<VRManager> sVRManagerSingleton;
-
 /**
  * When VR content is active, we run the tasks at 1ms
  * intervals, enabling multiple events to be processed
  * per frame, such as haptic feedback pulses.
  */
 const uint32_t kVRActiveTaskInterval = 1;  // milliseconds
 
 /**
  * When VR content is inactive, we run the tasks at 100ms
  * intervals, enabling VR display enumeration and
  * presentation startup to be relatively responsive
  * while not consuming unnecessary resources.
  */
 const uint32_t kVRIdleTaskInterval = 100;  // milliseconds
 
-/*static*/
-void VRManager::ManagerInit() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // TODO: We should make VRManager::ManagerInit
-  // be called when entering VR content pages.
-  if (sVRManagerSingleton == nullptr) {
-    sVRManagerSingleton = new VRManager();
-    ClearOnShutdown(&sVRManagerSingleton);
-  }
-}
-
-VRManager::VRManager()
-    : mInitialized(false),
-      mAccumulator100ms(0.0f),
-      mVRDisplaysRequested(false),
-      mVRDisplaysRequestedNonFocus(false),
-      mVRControllersRequested(false),
-      mVRServiceStarted(false),
-      mTaskInterval(0) {
-  MOZ_COUNT_CTOR(VRManager);
-  MOZ_ASSERT(sVRManagerSingleton == nullptr);
-
-  RefPtr<VRSystemManager> mgr;
-
-#if !defined(MOZ_WIDGET_ANDROID)
-  // The VR Service accesses all hardware from a separate process
-  // and replaces the other VRSystemManager when enabled.
-  if (!StaticPrefs::dom_vr_process_enabled() || !XRE_IsGPUProcess()) {
-    VRServiceManager::Get().CreateService();
-  }
-  if (VRServiceManager::Get().IsServiceValid()) {
-    mExternalManager =
-        VRSystemManagerExternal::Create(VRServiceManager::Get().GetAPIShmem());
-  }
-  if (mExternalManager) {
-    mManagers.AppendElement(mExternalManager);
-  }
-#endif
-
-  if (!mExternalManager) {
-    mExternalManager = VRSystemManagerExternal::Create();
-    if (mExternalManager) {
-      mManagers.AppendElement(mExternalManager);
-    }
-  }
+/**
+ * Max frame duration before the watchdog submits a new one.
+ * Probably we can get rid of this when we enforce that SubmitFrame can only be
+ * called in a VRDisplay loop.
+ */
+const double kVRMaxFrameSubmitDuration = 4000.0f;  // milliseconds
 
-  // Enable gamepad extensions while VR is enabled.
-  // Preference only can be set at the Parent process.
-  if (XRE_IsParentProcess() && StaticPrefs::dom_vr_enabled()) {
-    Preferences::SetBool("dom.gamepad.extensions.enabled", true);
-  }
-}
-
-VRManager::~VRManager() {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mInitialized);
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (VRServiceManager::Get().IsServiceValid()) {
-    VRServiceManager::Get().Shutdown();
-  }
-#endif
-  MOZ_COUNT_DTOR(VRManager);
-}
-
-void VRManager::Destroy() {
-  StopTasks();
-  mVRDisplayIDs.Clear();
-  mVRControllerIDs.Clear();
-  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
-    mManagers[i]->Destroy();
-  }
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (VRServiceManager::Get().IsServiceValid()) {
-    VRServiceManager::Get().Shutdown();
-  }
-#endif
-  Shutdown();
-  mInitialized = false;
-}
-
-void VRManager::Shutdown() {
-  mVRDisplayIDs.Clear();
-  mVRControllerIDs.Clear();
-  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
-    mManagers[i]->Shutdown();
-  }
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (VRServiceManager::Get().IsServiceValid()) {
-    VRServiceManager::Get().Stop();
-  }
-  // XRE_IsGPUProcess() is helping us to check some platforms like
-  // Win 7 try which are not using GPU process but VR process is enabled.
-  if (XRE_IsGPUProcess() && StaticPrefs::dom_vr_process_enabled() &&
-      mVRServiceStarted) {
-    RefPtr<Runnable> task = NS_NewRunnableFunction(
-        "VRServiceManager::ShutdownVRProcess",
-        []() -> void { VRServiceManager::Get().ShutdownVRProcess(); });
-    NS_DispatchToMainThread(task.forget());
-  }
-#endif
-  mVRServiceStarted = false;
-}
-
-void VRManager::Init() { mInitialized = true; }
+static StaticRefPtr<VRManager> sVRManagerSingleton;
 
 /* static */
 VRManager* VRManager::Get() {
   MOZ_ASSERT(sVRManagerSingleton != nullptr);
 
   return sVRManagerSingleton;
 }
 
+Atomic<uint32_t> VRManager::sDisplayBase(0);
+
+/* static */
+uint32_t VRManager::AllocateDisplayID() { return ++sDisplayBase; }
+
+/*static*/
+void VRManager::ManagerInit() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Enable gamepad extensions while VR is enabled.
+  // Preference only can be set at the Parent process.
+  if (StaticPrefs::dom_vr_enabled() && XRE_IsParentProcess()) {
+    Preferences::SetBool("dom.gamepad.extensions.enabled", true);
+  }
+
+  if (sVRManagerSingleton == nullptr) {
+    sVRManagerSingleton = new VRManager();
+    ClearOnShutdown(&sVRManagerSingleton);
+  }
+}
+
+VRManager::VRManager()
+    : mState(VRManagerState::Disabled),
+      mAccumulator100ms(0.0f),
+      mVRDisplaysRequested(false),
+      mVRDisplaysRequestedNonFocus(false),
+      mVRControllersRequested(false),
+      mFrameStarted(false),
+      mExternalShmem(nullptr),
+      mTaskInterval(0),
+      mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"),
+      mCurrentSubmitTask(nullptr),
+      mLastSubmittedFrameId(0),
+      mLastStartedFrame(0),
+      mEnumerationCompleted(false),
+#if defined(XP_MACOSX)
+      mShmemFD(0),
+#elif defined(XP_WIN)
+      mShmemFile(NULL),
+      mMutex(NULL),
+#endif
+      mHapticPulseRemaining{},
+      mDisplayInfo{},
+      mLastUpdateDisplayInfo{},
+      mBrowserState{},
+      mLastSensorState{} {
+  MOZ_COUNT_CTOR(VRManager);
+  MOZ_ASSERT(sVRManagerSingleton == nullptr);
+  MOZ_ASSERT(NS_IsMainThread());
+
+#if !defined(MOZ_WIDGET_ANDROID)
+  // XRE_IsGPUProcess() is helping us to check some platforms like
+  // Win 7 try which are not using GPU process but VR process is enabled.
+  mVRProcessEnabled =
+      StaticPrefs::dom_vr_process_enabled() && XRE_IsGPUProcess();
+  VRServiceHost::Init(mVRProcessEnabled);
+  mServiceHost = VRServiceHost::Get();
+  // We must shutdown before VRServiceHost, which is cleared
+  // on ShutdownPhase::ShutdownFinal, potentially before VRManager.
+  // We hold a reference to VRServiceHost to ensure it stays
+  // alive until we have shut down.
+#endif  // !defined(MOZ_WIDGET_ANDROID)
+}
+
+void VRManager::OpenShmem() {
+  if (mExternalShmem) {
+    return;
+  }
+#if defined(XP_WIN)
+  if (mMutex == NULL) {
+    mMutex = CreateMutex(NULL,   // default security descriptor
+                         false,  // mutex not owned
+                         TEXT("mozilla::vr::ShmemMutex"));  // object name
+    if (mMutex == NULL) {
+      nsAutoCString msg;
+      msg.AppendPrintf("VRManager CreateMutex error \"%lu\".", GetLastError());
+      NS_WARNING(msg.get());
+      MOZ_ASSERT(false);
+      return;
+    }
+    // At xpcshell extension tests, it creates multiple VRManager
+    // instances in plug-contrainer.exe. It causes GetLastError() return
+    // `ERROR_ALREADY_EXISTS`. However, even though `ERROR_ALREADY_EXISTS`, it
+    // still returns the same mutex handle.
+    //
+    // https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-createmutexa
+    MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS);
+  }
+#endif  // XP_WIN
+#if !defined(MOZ_WIDGET_ANDROID)
+  // The VR Service accesses all hardware from a separate process
+  // and replaces the other VRManager when enabled.
+  // If the VR process is not enabled, create an in-process VRService.
+  if (!mVRProcessEnabled) {
+    // If the VR process is disabled, attempt to create a
+    // VR service within the current process
+    mExternalShmem = new VRExternalShmem();
+    // VRExternalShmem is asserted to be POD
+    mExternalShmem->Clear();
+    mServiceHost->CreateService(mExternalShmem);
+    return;
+  }
+#endif
+
+#if defined(XP_MACOSX)
+  if (mShmemFD == 0) {
+    mShmemFD =
+        shm_open(kShmemName, O_RDWR, S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH);
+  }
+  if (mShmemFD <= 0) {
+    mShmemFD = 0;
+    return;
+  }
+
+  struct stat sb;
+  fstat(mShmemFD, &sb);
+  off_t length = sb.st_size;
+  if (length < (off_t)sizeof(VRExternalShmem)) {
+    // TODO - Implement logging (Bug 1558912)
+    CloseShmem();
+    return;
+  }
+
+  mExternalShmem = (VRExternalShmem*)mmap(NULL, length, PROT_READ | PROT_WRITE,
+                                          MAP_SHARED, mShmemFD, 0);
+  if (mExternalShmem == MAP_FAILED) {
+    // TODO - Implement logging (Bug 1558912)
+    mExternalShmem = NULL;
+    CloseShmem();
+    return;
+  }
+
+#elif defined(XP_WIN)
+  if (mShmemFile == NULL) {
+    mShmemFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
+                                    0, sizeof(VRExternalShmem), kShmemName);
+    MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS);
+    MOZ_ASSERT(mShmemFile);
+    if (mShmemFile == NULL) {
+      // TODO - Implement logging (Bug 1558912)
+      CloseShmem();
+      return;
+    }
+  }
+  LARGE_INTEGER length;
+  length.QuadPart = sizeof(VRExternalShmem);
+  mExternalShmem = (VRExternalShmem*)MapViewOfFile(
+      mShmemFile,           // handle to map object
+      FILE_MAP_ALL_ACCESS,  // read/write permission
+      0, 0, length.QuadPart);
+
+  if (mExternalShmem == NULL) {
+    // TODO - Implement logging (Bug 1558912)
+    CloseShmem();
+    return;
+  }
+#elif defined(MOZ_WIDGET_ANDROID)
+  mExternalShmem =
+      (VRExternalShmem*)mozilla::GeckoVRManager::GetExternalContext();
+  if (!mExternalShmem) {
+    return;
+  }
+  int32_t version = -1;
+  int32_t size = 0;
+  if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
+      0) {
+    version = mExternalShmem->version;
+    size = mExternalShmem->size;
+    pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
+  } else {
+    return;
+  }
+  if (version != kVRExternalVersion) {
+    mExternalShmem = nullptr;
+    return;
+  }
+  if (size != sizeof(VRExternalShmem)) {
+    mExternalShmem = nullptr;
+    return;
+  }
+#endif
+}
+
+void VRManager::CloseShmem() {
+#if !defined(MOZ_WIDGET_ANDROID)
+  if (!mVRProcessEnabled) {
+    if (mExternalShmem) {
+      delete mExternalShmem;
+      mExternalShmem = nullptr;
+    }
+    return;
+  }
+#endif
+#if defined(XP_MACOSX)
+  if (mExternalShmem) {
+    munmap((void*)mExternalShmem, sizeof(VRExternalShmem));
+    mExternalShmem = NULL;
+  }
+  if (mShmemFD) {
+    close(mShmemFD);
+    mShmemFD = 0;
+  }
+#elif defined(XP_WIN)
+  if (mExternalShmem) {
+    UnmapViewOfFile((void*)mExternalShmem);
+    mExternalShmem = NULL;
+  }
+  if (mShmemFile) {
+    CloseHandle(mShmemFile);
+    mShmemFile = NULL;
+  }
+#elif defined(MOZ_WIDGET_ANDROID)
+  mExternalShmem = NULL;
+#endif
+}
+
+VRManager::~VRManager() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == VRManagerState::Disabled);
+#if !defined(MOZ_WIDGET_ANDROID)
+  mServiceHost->Shutdown();
+#endif
+  CloseShmem();
+#if defined(XP_WIN)
+  if (mMutex) {
+    CloseHandle(mMutex);
+    mMutex = NULL;
+  }
+#endif
+  MOZ_COUNT_DTOR(VRManager);
+}
+
+void VRManager::AddLayer(VRLayerParent* aLayer) {
+  mLayers.AppendElement(aLayer);
+  mDisplayInfo.mPresentingGroups |= aLayer->GetGroup();
+  if (mLayers.Length() == 1) {
+    StartPresentation();
+  }
+
+  // Ensure that the content process receives the change immediately
+  RefreshVRDisplays();
+}
+
+void VRManager::RemoveLayer(VRLayerParent* aLayer) {
+  mLayers.RemoveElement(aLayer);
+  if (mLayers.Length() == 0) {
+    StopPresentation();
+  }
+  mDisplayInfo.mPresentingGroups = 0;
+  for (auto layer : mLayers) {
+    mDisplayInfo.mPresentingGroups |= layer->GetGroup();
+  }
+
+  // Ensure that the content process receives the change immediately
+  RefreshVRDisplays();
+}
+
 void VRManager::AddVRManagerParent(VRManagerParent* aVRManagerParent) {
-  if (mVRManagerParents.IsEmpty()) {
-    Init();
-  }
   mVRManagerParents.PutEntry(aVRManagerParent);
 }
 
 void VRManager::RemoveVRManagerParent(VRManagerParent* aVRManagerParent) {
   mVRManagerParents.RemoveEntry(aVRManagerParent);
   if (mVRManagerParents.IsEmpty()) {
     Destroy();
   }
@@ -200,18 +389,25 @@ void VRManager::UpdateRequestedDevices()
 
 /**
  * VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz).
  * This must be called even when no WebVR site is active.
  * If we don't have a 2d display attached to the system, we can call this
  * at the VR display's native refresh rate.
  **/
 void VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) {
-  for (const auto& manager : mManagers) {
-    manager->NotifyVSync();
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  /**
+   * If the display isn't presenting, refresh the sensors and trigger
+   * VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
+   */
+  if (mDisplayInfo.mPresentingGroups == 0) {
+    StartFrame();
   }
 }
 
 void VRManager::StartTasks() {
   if (!mTaskTimer) {
     mTaskInterval = GetOptimalTaskInterval();
     mTaskTimer = NS_NewTimer();
     mTaskTimer->SetTarget(CompositorThreadHolder::Loop()->SerialEventTarget());
@@ -229,30 +425,30 @@ void VRManager::StopTasks() {
   }
 }
 
 /*static*/
 void VRManager::TaskTimerCallback(nsITimer* aTimer, void* aClosure) {
   /**
    * It is safe to use the pointer passed in aClosure to reference the
    * VRManager object as the timer is canceled in VRManager::Destroy.
-   * VRManager::Destroy set mInitialized to false, which is asserted
-   * in the VRManager destructor, guaranteeing that this functions
-   * runs if and only if the VRManager object is valid.
+   * VRManager::Destroy set mState to VRManagerState::Disabled, which
+   * is asserted in the VRManager destructor, guaranteeing that this
+   * functions runs if and only if the VRManager object is valid.
    */
   VRManager* self = static_cast<VRManager*>(aClosure);
   self->RunTasks();
 }
 
 void VRManager::RunTasks() {
   // Will be called once every 1ms when a VR presentation
   // is active or once per vsync when a VR presentation is
   // not active.
 
-  if (!mInitialized) {
+  if (mState == VRManagerState::Disabled) {
     // We may have been destroyed but still have messages
     // in the queue from mTaskTimer.  Bail out to avoid
     // running them.
     return;
   }
 
   TimeStamp now = TimeStamp::Now();
   double lastTickMs = mAccumulator100ms;
@@ -291,96 +487,68 @@ void VRManager::RunTasks() {
 
 uint32_t VRManager::GetOptimalTaskInterval() {
   /**
    * When either VR content is detected or VR hardware
    * has already been activated, we schedule tasks more
    * frequently.
    */
   bool wantGranularTasks = mVRDisplaysRequested || mVRControllersRequested ||
-                           mVRDisplayIDs.Length() || mVRControllerIDs.Length();
+                           mDisplayInfo.mDisplayID != 0;
   if (wantGranularTasks) {
     return kVRActiveTaskInterval;
   }
 
   return kVRIdleTaskInterval;
 }
 
 /**
  * Run1msTasks() is guaranteed not to be
  * called more than once within 1ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
  * called within the last 1ms.
  */
-void VRManager::Run1msTasks(double aDeltaTime) {
-  for (const auto& manager : mManagers) {
-    manager->Run1msTasks(aDeltaTime);
-  }
-
-  for (const auto& displayID : mVRDisplayIDs) {
-    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
-    if (display) {
-      display->Run1msTasks(aDeltaTime);
-    }
-  }
-}
+void VRManager::Run1msTasks(double aDeltaTime) { UpdateHaptics(aDeltaTime); }
 
 /**
  * Run10msTasks() is guaranteed not to be
  * called more than once within 10ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
  * called within the last 10ms.
  */
 void VRManager::Run10msTasks() {
   UpdateRequestedDevices();
-
-  for (const auto& manager : mManagers) {
-    manager->Run10msTasks();
-  }
-
-  for (const auto& displayID : mVRDisplayIDs) {
-    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
-    if (display) {
-      display->Run10msTasks();
-    }
-  }
+  CheckWatchDog();
+  ExpireNavigationTransition();
+  PullState();
+  PushState();
 }
 
 /**
  * Run100msTasks() is guaranteed not to be
  * called more than once within 100ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
  * called within the last 100ms.
  */
 void VRManager::Run100msTasks() {
   // We must continually refresh the VR display enumeration to check
   // for events that we must fire such as Window.onvrdisplayconnect
   // Note that enumeration itself may activate display hardware, such
   // as Oculus, so we only do this when we know we are displaying content
   // that is looking for VR displays.
+#if !defined(MOZ_WIDGET_ANDROID)
+  mServiceHost->Refresh();
+  CheckForPuppetCompletion();
+#endif
   RefreshVRDisplays();
-
-  // Update state and enumeration of VR controllers
-  RefreshVRControllers();
-
   CheckForInactiveTimeout();
-
-  for (const auto& manager : mManagers) {
-    manager->Run100msTasks();
-  }
-
-  for (const auto& displayID : mVRDisplayIDs) {
-    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
-    if (display) {
-      display->Run100msTasks();
-    }
-  }
+  CheckForShutdown();
 }
 
 void VRManager::CheckForInactiveTimeout() {
   // Shut down the VR devices when not in use
   if (mVRDisplaysRequested || mVRDisplaysRequestedNonFocus ||
       mVRControllersRequested) {
     // We are using a VR device, keep it alive
     mLastActiveTime = TimeStamp::Now();
@@ -395,418 +563,920 @@ void VRManager::CheckForInactiveTimeout(
       // user needing to refresh the browser to detect
       // VR hardware when leaving and returning to a VR
       // site.
       mLastDisplayEnumerationTime = TimeStamp();
     }
   }
 }
 
-void VRManager::NotifyVRVsync(const uint32_t& aDisplayID) {
-  for (const auto& manager : mManagers) {
-    if (manager->GetIsPresenting()) {
-      manager->HandleInput();
+void VRManager::CheckForShutdown() {
+  // Check for remote end shutdown
+  if (mState != VRManagerState::Disabled && mState != VRManagerState::Idle &&
+      mDisplayInfo.mDisplayState.shutdown) {
+    Shutdown();
+  }
+}
+
+#if !defined(MOZ_WIDGET_ANDROID)
+void VRManager::CheckForPuppetCompletion() {
+  // Notify content process about completion of puppet test resets
+  if (mState != VRManagerState::Active) {
+    for (auto iter = mManagerParentsWaitingForPuppetReset.Iter(); !iter.Done();
+         iter.Next()) {
+      Unused << iter.Get()->GetKey()->SendNotifyPuppetResetComplete();
+    }
+    mManagerParentsWaitingForPuppetReset.Clear();
+  }
+  // Notify content process about completion of puppet test scripts
+  if (mManagerParentRunningPuppet) {
+    if (mServiceHost->PuppetHasEnded()) {
+      Unused << mManagerParentRunningPuppet
+                    ->SendNotifyPuppetCommandBufferCompleted(true);
+      mManagerParentRunningPuppet = nullptr;
     }
   }
+}
+#endif  // !defined(MOZ_WIDGET_ANDROID)
 
-  RefPtr<VRDisplayHost> display = GetDisplay(aDisplayID);
-  if (display) {
-    display->StartFrame();
+void VRManager::StartFrame() {
+  if (mState != VRManagerState::Active) {
+    return;
   }
+  AUTO_PROFILER_TRACING("VR", "GetSensorState", OTHER);
+
+  /**
+   * Do not start more VR frames until the last submitted frame is already
+   * processed, or the last has stalled for more than
+   * kVRMaxFrameSubmitDuration milliseconds.
+   */
+  TimeStamp now = TimeStamp::Now();
+  const TimeStamp lastFrameStart =
+      mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
+  const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
+  double duration =
+      lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
+  if (isPresenting && mLastStartedFrame > 0 &&
+      mDisplayInfo.mDisplayState.lastSubmittedFrameId < mLastStartedFrame &&
+      duration < kVRMaxFrameSubmitDuration) {
+    return;
+  }
+
+  mDisplayInfo.mFrameId++;
+  size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
+  mDisplayInfo.mLastSensorState[bufferIndex] = mLastSensorState;
+  mLastFrameStart[bufferIndex] = now;
+  mFrameStarted = true;
+  mLastStartedFrame = mDisplayInfo.mFrameId;
 
   DispatchVRDisplayInfoUpdate();
 }
 
 void VRManager::EnumerateVRDisplays() {
-  StartTasks();
-  /**
-   * Throttle the rate of enumeration to the interval set in
-   * dom_vr_display_enumerate_interval.
-   */
-  if (!mLastDisplayEnumerationTime.IsNull()) {
-    TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime;
-    if (duration.ToMilliseconds() <
-        StaticPrefs::dom_vr_display_enumerate_interval()) {
-      return;
-    }
+  if (!StaticPrefs::dom_vr_enabled()) {
+    return;
   }
 
-  /**
-   * Any VRSystemManager instance may request that no enumeration
-   * should occur, including enumeration from other VRSystemManager
-   * instances.
-   */
-  for (const auto& manager : mManagers) {
-    if (manager->ShouldInhibitEnumeration()) {
-      return;
-    }
+  if (mState == VRManagerState::Disabled) {
+    StartTasks();
+    mState = VRManagerState::Idle;
   }
 
-  /**
-   * If we get this far, don't try again until the
-   * dom_vr_display_enumerate_interval elapses.
-   */
-  mLastDisplayEnumerationTime = TimeStamp::Now();
-
-  /**
-   * We must start the VR Service thread
-   * and VR Process before enumeration.
-   * We don't want to start this until we will
-   * actualy enumerate, to avoid continuously
-   * re-launching the thread/process when
-   * no hardware is found or a VR software update
-   * is in progress
-   */
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (!mVRServiceStarted) {
-    if (XRE_IsGPUProcess() && StaticPrefs::dom_vr_process_enabled()) {
-      VRServiceManager::Get().CreateVRProcess();
-      mVRServiceStarted = true;
-    } else {
-      if (VRServiceManager::Get().IsServiceValid()) {
-        VRServiceManager::Get().Start();
-        mVRServiceStarted = true;
+  if (mState == VRManagerState::Idle) {
+    /**
+     * Throttle the rate of enumeration to the interval set in
+     * VRDisplayEnumerateInterval
+     */
+    if (!mLastDisplayEnumerationTime.IsNull()) {
+      TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime;
+      if (duration.ToMilliseconds() <
+          StaticPrefs::dom_vr_display_enumerate_interval()) {
+        return;
       }
     }
-  }
-#endif
 
-  /**
-   * VRSystemManagers are inserted into mManagers in
-   * a strict order of priority.  The managers for the
-   * most device-specialized API's will have a chance
-   * to enumerate devices before the more generic
-   * device-agnostic APIs.
-   */
-  for (const auto& manager : mManagers) {
-    manager->Enumerate();
-    /**
-     * After a VRSystemManager::Enumerate is called, it may request
-     * that further enumeration should stop.  This can be used to prevent
-     * erraneous redundant enumeration of the same HMD by multiple managers.
-     * XXX - Perhaps there will be a better way to detect duplicate displays
-     * in the future.
-     */
-    if (manager->ShouldInhibitEnumeration()) {
+    if (!mEarliestRestartTime.IsNull() &&
+        mEarliestRestartTime > TimeStamp::Now()) {
+      // When the VR Service shuts down it informs us of how long we
+      // must wait until we can re-start it.
+      // We must wait until mEarliestRestartTime before attempting
+      // to enumerate again.
       return;
     }
-  }
 
-  nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
-  for (const auto& manager : mManagers) {
-    manager->GetHMDs(displays);
-  }
+    /**
+     * If we get this far, don't try again until
+     * the VRDisplayEnumerateInterval elapses
+     */
+    mLastDisplayEnumerationTime = TimeStamp::Now();
+
+    OpenShmem();
 
-  mVRDisplayIDs.Clear();
-  for (const auto& display : displays) {
-    mVRDisplayIDs.AppendElement(display->GetDisplayInfo().GetDisplayID());
-  }
+    /**
+     * We must start the VR Service thread
+     * and VR Process before enumeration.
+     * We don't want to start this until we will
+     * actualy enumerate, to avoid continuously
+     * re-launching the thread/process when
+     * no hardware is found or a VR software update
+     * is in progress
+     */
+#if !defined(MOZ_WIDGET_ANDROID)
+    mServiceHost->StartService();
+#endif
+    if (mExternalShmem) {
+      mDisplayInfo.Clear();
+      mLastUpdateDisplayInfo.Clear();
+      mFrameStarted = false;
+      mBrowserState.Clear();
+      mLastSensorState.Clear();
+      mDisplayInfo.mGroupMask = kVRGroupContent;
+      // We must block until enumeration has completed in order
+      // to signal that the WebVR promise should be resolved at the
+      // right time.
+      mState = VRManagerState::Enumeration;
+    }
+  }  // if (mState == VRManagerState::Idle)
 
-  nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
-  for (const auto& manager : mManagers) {
-    manager->GetControllers(controllers);
-  }
+  if (mState == VRManagerState::Enumeration) {
+    MOZ_ASSERT(mExternalShmem != nullptr);
 
-  mVRControllerIDs.Clear();
-  for (const auto& controller : controllers) {
-    mVRControllerIDs.AppendElement(
-        controller->GetControllerInfo().GetControllerID());
-  }
+    PullState();
+    if (mEnumerationCompleted) {
+      if (mDisplayInfo.mDisplayState.isConnected) {
+        mDisplayInfo.mDisplayID = VRManager::AllocateDisplayID();
+        mState = VRManagerState::Active;
+      } else {
+        Shutdown();
+      }
+    }
+  }  // if (mState == VRManagerState::Enumeration)
 }
 
 void VRManager::RefreshVRDisplays(bool aMustDispatch) {
+  uint32_t previousDisplayID = mDisplayInfo.GetDisplayID();
+
   /**
    * If we aren't viewing WebVR content, don't enumerate
    * new hardware, as it will cause some devices to power on
    * or interrupt other VR activities.
    */
   if (mVRDisplaysRequested || aMustDispatch) {
     EnumerateVRDisplays();
   }
-#if !defined(MOZ_WIDGET_ANDROID)
-  VRServiceManager::Get().Refresh();
-#endif
+  if (mState == VRManagerState::Enumeration) {
+    // If we are enumerating VR Displays, do not dispatch
+    // updates until the enumeration has completed.
+    return;
+  }
+  bool changed = false;
 
-  /**
-   * VRSystemManager::GetHMDs will not activate new hardware
-   * or result in interruption of other VR activities.
-   * We can call it even when suppressing enumeration to get
-   * the already-enumerated displays.
-   */
-  nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
-  for (const auto& manager : mManagers) {
-    manager->GetHMDs(displays);
-  }
-
-  bool displayInfoChanged = false;
-  bool displaySetChanged = false;
-
-  if (displays.Length() != mVRDisplayIDs.Length()) {
-    // Catch cases where a VR display has been removed
-    displaySetChanged = true;
+  if (previousDisplayID != mDisplayInfo.GetDisplayID()) {
+    changed = true;
   }
 
-  for (const auto& display : displays) {
-    if (!GetDisplay(display->GetDisplayInfo().GetDisplayID())) {
-      // This is a new display
-      displaySetChanged = true;
-      break;
-    }
-
-    if (display->CheckClearDisplayInfoDirty()) {
-      // This display's info has changed
-      displayInfoChanged = true;
-      break;
-    }
+  if (mState == VRManagerState::Active &&
+      mDisplayInfo != mLastUpdateDisplayInfo) {
+    // This display's info has changed
+    changed = true;
   }
 
-  // Rebuild the HashMap if there are additions or removals
-  if (displaySetChanged) {
-    mVRDisplayIDs.Clear();
-    for (const auto& display : displays) {
-      mVRDisplayIDs.AppendElement(display->GetDisplayInfo().GetDisplayID());
-    }
-  }
-
-  if (displayInfoChanged || displaySetChanged || aMustDispatch) {
+  if (changed || aMustDispatch) {
     DispatchVRDisplayInfoUpdate();
   }
 }
 
 void VRManager::DispatchVRDisplayInfoUpdate() {
-  nsTArray<VRDisplayInfo> update;
-  GetVRDisplayInfo(update);
-
-  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
-    Unused << iter.Get()->GetKey()->SendUpdateDisplayInfo(update);
-  }
-}
-
-/**
- * Get any VR displays that have already been enumerated without
- * activating any new devices.
- */
-void VRManager::GetVRDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayInfo) {
-  aDisplayInfo.Clear();
-  for (const auto& displayID : mVRDisplayIDs) {
-    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
-    if (display) {
-      aDisplayInfo.AppendElement(display->GetDisplayInfo());
-    }
-  }
-}
-
-// Verify aDisplayID matches the exisiting displayID from mVRDisplayIDs list,
-// then using the exiting displayID to get VRDisplayHost from VRManagers.
-RefPtr<gfx::VRDisplayHost> VRManager::GetDisplay(const uint32_t& aDisplayID) {
-  bool found = false;
-  for (const auto& displayID : mVRDisplayIDs) {
-    if (displayID == aDisplayID) {
-      found = true;
-      break;
-    }
+  // This could be simplified further by only supporting one display
+  nsTArray<VRDisplayInfo> displayUpdates;
+  if (mState == VRManagerState::Active) {
+    MOZ_ASSERT(mDisplayInfo.mDisplayID != 0);
+    displayUpdates.AppendElement(mDisplayInfo);
   }
-
-  if (found) {
-    nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
-    for (const auto& manager : mManagers) {
-      manager->GetHMDs(displays);
-      for (const auto& display : displays) {
-        if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) {
-          return display;
-        }
-      }
-    }
+  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
+    Unused << iter.Get()->GetKey()->SendUpdateDisplayInfo(displayUpdates);
   }
-
-  return nullptr;
-}
-
-// Verify aControllerID matches the exisiting controllerID from mVRControllerIDs
-// list, then using the exiting controllerID to get VRControllerHost from
-// VRManagers.
-RefPtr<gfx::VRControllerHost> VRManager::GetController(
-    const uint32_t& aControllerID) {
-  bool found = false;
-  for (const auto& controllerID : mVRControllerIDs) {
-    if (controllerID == aControllerID) {
-      found = true;
-      break;
-    }
-  }
-
-  if (found) {
-    nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
-    for (const auto& manager : mManagers) {
-      manager->GetControllers(controllers);
-      for (const auto& controller : controllers) {
-        if (controller->GetControllerInfo().GetControllerID() ==
-            aControllerID) {
-          return controller;
-        }
-      }
-    }
-  }
-
-  return nullptr;
-}
-
-void VRManager::GetVRControllerInfo(
-    nsTArray<VRControllerInfo>& aControllerInfo) {
-  aControllerInfo.Clear();
-  for (const auto& controllerID : mVRControllerIDs) {
-    RefPtr<VRControllerHost> controller(GetController(controllerID));
-    if (controller) {
-      aControllerInfo.AppendElement(controller->GetControllerInfo());
-    }
-  }
+  mLastUpdateDisplayInfo = mDisplayInfo;
 }
 
-void VRManager::RefreshVRControllers() {
-  ScanForControllers();
-
-  nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
-
-  for (uint32_t i = 0; i < mManagers.Length() && controllers.Length() == 0;
-       ++i) {
-    mManagers[i]->GetControllers(controllers);
-  }
-
-  bool controllerInfoChanged = false;
-
-  if (controllers.Length() != mVRControllerIDs.Length()) {
-    // Catch cases where VR controllers has been removed
-    controllerInfoChanged = true;
-  }
-
-  for (const auto& controller : controllers) {
-    if (!GetController(controller->GetControllerInfo().GetControllerID())) {
-      // This is a new controller
-      controllerInfoChanged = true;
-      break;
-    }
-  }
-
-  if (controllerInfoChanged) {
-    mVRControllerIDs.Clear();
-    for (const auto& controller : controllers) {
-      mVRControllerIDs.AppendElement(
-          controller->GetControllerInfo().GetControllerID());
-    }
-  }
-}
-
-void VRManager::ScanForControllers() {
-  // We don't have to do this every frame, so check if we
-  // have enumerated recently
-  if (!mLastControllerEnumerationTime.IsNull()) {
-    TimeDuration duration = TimeStamp::Now() - mLastControllerEnumerationTime;
-    if (duration.ToMilliseconds() <
-        StaticPrefs::dom_vr_controller_enumerate_interval()) {
-      return;
-    }
+void VRManager::StopAllHaptics() {
+  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
+    ClearHapticSlot(i);
   }
-
-  // Only enumerate controllers once we need them
-  if (!mVRControllersRequested) {
-    return;
-  }
-
-  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
-    mManagers[i]->ScanForControllers();
-  }
-
-  mLastControllerEnumerationTime = TimeStamp::Now();
-}
-
-void VRManager::RemoveControllers() {
-  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
-    mManagers[i]->RemoveControllers();
-  }
-  mVRControllerIDs.Clear();
-}
-
-void VRManager::CreateVRTestSystem() {
-  if (mPuppetManager) {
-    mPuppetManager->ClearTestDisplays();
-    return;
-  }
-
-  mPuppetManager = VRSystemManagerPuppet::Create();
-  mManagers.AppendElement(mPuppetManager);
-}
-
-VRSystemManagerPuppet* VRManager::GetPuppetManager() {
-  MOZ_ASSERT(mPuppetManager);
-  return mPuppetManager;
-}
-
-VRSystemManagerExternal* VRManager::GetExternalManager() {
-  MOZ_ASSERT(mExternalManager);
-  return mExternalManager;
-}
-
-template <class T>
-void VRManager::NotifyGamepadChange(uint32_t aIndex, const T& aInfo) {
-  dom::GamepadChangeEventBody body(aInfo);
-  dom::GamepadChangeEvent e(aIndex, dom::GamepadServiceType::VR, body);
-
-  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
-    Unused << iter.Get()->GetKey()->SendGamepadUpdate(e);
-  }
+  PushState();
 }
 
 void VRManager::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                               double aIntensity, double aDuration,
                               const VRManagerPromise& aPromise)
 
 {
-  for (uint32_t i = 0; i < mManagers.Length(); ++i) {
-    mManagers[i]->VibrateHaptic(aControllerIdx, aHapticIndex, aIntensity,
-                                aDuration, aPromise);
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  // VRDisplayClient::FireGamepadEvents() assigns a controller ID with
+  // ranges based on displayID.  We must translate this to the indexes
+  // understood by VRDisplayExternal.
+  uint32_t controllerBaseIndex =
+      kVRControllerMaxCount * mDisplayInfo.mDisplayID;
+  uint32_t controllerIndex = aControllerIdx - controllerBaseIndex;
+
+  TimeStamp now = TimeStamp::Now();
+  size_t bestSlotIndex = 0;
+  // Default to an empty slot, or the slot holding the oldest haptic pulse
+  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
+    const VRHapticState& state = mBrowserState.hapticState[i];
+    if (state.inputFrameID == 0) {
+      // Unused slot, use it
+      bestSlotIndex = i;
+      break;
+    }
+    if (mHapticPulseRemaining[i] < mHapticPulseRemaining[bestSlotIndex]) {
+      // If no empty slots are available, fall back to overriding
+      // the pulse which is ending soonest.
+      bestSlotIndex = i;
+    }
   }
+  // Override the last pulse on the same actuator if present.
+  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
+    const VRHapticState& state = mBrowserState.hapticState[i];
+    if (state.inputFrameID == 0) {
+      // This is an empty slot -- no match
+      continue;
+    }
+    if (state.controllerIndex == controllerIndex &&
+        state.hapticIndex == aHapticIndex) {
+      // Found pulse on same actuator -- let's override it.
+      bestSlotIndex = i;
+    }
+  }
+  ClearHapticSlot(bestSlotIndex);
+
+  // Populate the selected slot with new haptic state
+  size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
+  VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex];
+  bestSlot.inputFrameID =
+      mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID;
+  bestSlot.controllerIndex = controllerIndex;
+  bestSlot.hapticIndex = aHapticIndex;
+  bestSlot.pulseStart = (float)(now - mLastFrameStart[bufferIndex]).ToSeconds();
+  bestSlot.pulseDuration =
+      (float)aDuration * 0.001f;  // Convert from ms to seconds
+  bestSlot.pulseIntensity = (float)aIntensity;
+
+  mHapticPulseRemaining[bestSlotIndex] = aDuration;
+  MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length());
+  if (bestSlotIndex == mHapticPromises.Length()) {
+    mHapticPromises.AppendElement(
+        UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)));
+  } else {
+    mHapticPromises[bestSlotIndex] =
+        UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise));
+  }
+  PushState();
 }
 
 void VRManager::StopVibrateHaptic(uint32_t aControllerIdx) {
-  for (const auto& manager : mManagers) {
-    manager->StopVibrateHaptic(aControllerIdx);
+  if (mState != VRManagerState::Active) {
+    return;
   }
+  // VRDisplayClient::FireGamepadEvents() assigns a controller ID with
+  // ranges based on displayID.  We must translate this to the indexes
+  // understood by VRDisplayExternal.
+  uint32_t controllerBaseIndex =
+      kVRControllerMaxCount * mDisplayInfo.mDisplayID;
+  uint32_t controllerIndex = aControllerIdx - controllerBaseIndex;
+
+  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
+    VRHapticState& state = mBrowserState.hapticState[i];
+    if (state.controllerIndex == controllerIndex) {
+      memset(&state, 0, sizeof(VRHapticState));
+    }
+  }
+  PushState();
 }
 
 void VRManager::NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise) {
   aPromise.mParent->SendReplyGamepadVibrateHaptic(aPromise.mPromiseID);
 }
 
-void VRManager::DispatchSubmitFrameResult(
-    uint32_t aDisplayID, const VRSubmitFrameResultInfo& aResult) {
-  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
-    Unused << iter.Get()->GetKey()->SendDispatchSubmitFrameResult(aDisplayID,
-                                                                  aResult);
+void VRManager::StartVRNavigation(const uint32_t& aDisplayID) {
+  if (mState != VRManagerState::Active) {
+    return;
   }
-}
-
-void VRManager::StartVRNavigation(const uint32_t& aDisplayID) {
-  RefPtr<VRDisplayHost> display = GetDisplay(aDisplayID);
-  if (display) {
-    display->StartVRNavigation();
+  /**
+   * We only support a single VRSession with a single VR display at a
+   * time; however, due to the asynchronous nature of the API, it's possible
+   * that the previously used VR display was a different one than the one now
+   * allocated. We catch these cases to avoid automatically activating the new
+   * VR displays. This situation is expected to be very rare and possibly never
+   * seen. Perhaps further simplification could be made in the content process
+   * code which passes around displayID's that may no longer be needed.
+   **/
+  if (mDisplayInfo.GetDisplayID() != aDisplayID) {
+    return;
   }
+  mBrowserState.navigationTransitionActive = true;
+  mVRNavigationTransitionEnd = TimeStamp();
+  PushState();
 }
 
 void VRManager::StopVRNavigation(const uint32_t& aDisplayID,
                                  const TimeDuration& aTimeout) {
-  RefPtr<VRDisplayHost> display = GetDisplay(aDisplayID);
-  if (display) {
-    display->StopVRNavigation(aTimeout);
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  if (mDisplayInfo.GetDisplayID() != aDisplayID) {
+    return;
+  }
+  if (aTimeout.ToMilliseconds() <= 0) {
+    mBrowserState.navigationTransitionActive = false;
+    mVRNavigationTransitionEnd = TimeStamp();
+    PushState();
+  }
+  mVRNavigationTransitionEnd = TimeStamp::Now() + aTimeout;
+}
+
+#if !defined(MOZ_WIDGET_ANDROID)
+
+bool VRManager::RunPuppet(const InfallibleTArray<uint64_t>& aBuffer,
+                          VRManagerParent* aManagerParent) {
+  if (!StaticPrefs::dom_vr_puppet_enabled()) {
+    // Sanity check to ensure that a compromised content process
+    // can't use this to escalate permissions.
+    return false;
+  }
+  if (mManagerParentRunningPuppet != nullptr) {
+    // Only one parent may run a puppet at a time
+    return false;
+  }
+  mManagerParentRunningPuppet = aManagerParent;
+  mServiceHost->PuppetSubmit(aBuffer);
+  return true;
+}
+
+void VRManager::ResetPuppet(VRManagerParent* aManagerParent) {
+  mManagerParentsWaitingForPuppetReset.PutEntry(aManagerParent);
+  if (mManagerParentRunningPuppet != nullptr) {
+    Unused << mManagerParentRunningPuppet
+                  ->SendNotifyPuppetCommandBufferCompleted(false);
+    mManagerParentRunningPuppet = nullptr;
+  }
+  mServiceHost->PuppetReset();
+  // In the event that we are shut down, the task timer won't be running
+  // to trigger CheckForPuppetCompletion.
+  // In this case, CheckForPuppetCompletion() would immediately resolve
+  // the promises for mManagerParentsWaitingForPuppetReset.
+  // We can simply call it once here to handle that case.
+  CheckForPuppetCompletion();
+}
+
+#endif  // !defined(MOZ_WIDGET_ANDROID)
+
+#if defined(MOZ_WIDGET_ANDROID)
+void VRManager::PullState(
+    const std::function<bool()>& aWaitCondition /* = nullptr */) {
+  if (!mExternalShmem) {
+    return;
+  }
+  bool done = false;
+  while (!done) {
+    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
+        0) {
+      while (true) {
+        memcpy(&mDisplayInfo.mDisplayState,
+               (void*)&(mExternalShmem->state.displayState),
+               sizeof(VRDisplayState));
+        memcpy(&mLastSensorState, (void*)&(mExternalShmem->state.sensorState),
+               sizeof(VRHMDSensorState));
+        memcpy(mDisplayInfo.mControllerState,
+               (void*)&(mExternalShmem->state.controllerState),
+               sizeof(VRControllerState) * kVRControllerMaxCount);
+        mEnumerationCompleted = mExternalShmem->state.enumerationCompleted;
+        if (!aWaitCondition || aWaitCondition()) {
+          done = true;
+          break;
+        }
+        // Block current thead using the condition variable until data
+        // changes
+        pthread_cond_wait((pthread_cond_t*)&mExternalShmem->systemCond,
+                          (pthread_mutex_t*)&mExternalShmem->systemMutex);
+      }  // while (true)
+      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
+    } else if (!aWaitCondition) {
+      // pthread_mutex_lock failed and we are not waiting for a condition to
+      // exit from PullState call.
+      return;
+    }
+  }  // while (!done) {
+}
+#else
+
+void VRManager::PullState(
+    const std::function<bool()>& aWaitCondition /* = nullptr */) {
+  MOZ_ASSERT(mExternalShmem);
+  if (!mExternalShmem) {
+    return;
+  }
+  while (true) {
+    {  // Scope for WaitForMutex
+#  if defined(XP_WIN)
+      bool status = true;
+      WaitForMutex lock(mMutex);
+      status = lock.GetStatus();
+      if (status) {
+#  endif  // defined(XP_WIN)
+        VRExternalShmem tmp;
+        memcpy(&tmp, (void*)mExternalShmem, sizeof(VRExternalShmem));
+        bool isCleanCopy =
+            tmp.generationA == tmp.generationB && tmp.generationA != 0;
+        if (isCleanCopy) {
+          memcpy(&mDisplayInfo.mDisplayState, &tmp.state.displayState,
+                 sizeof(VRDisplayState));
+          memcpy(&mLastSensorState, &tmp.state.sensorState,
+                 sizeof(VRHMDSensorState));
+          memcpy(mDisplayInfo.mControllerState,
+                 (void*)&(mExternalShmem->state.controllerState),
+                 sizeof(VRControllerState) * kVRControllerMaxCount);
+          mEnumerationCompleted = mExternalShmem->state.enumerationCompleted;
+          // Check for wait condition
+          if (!aWaitCondition || aWaitCondition()) {
+            return;
+          }
+        }  // if (isCleanCopy)
+        // Yield the thread while polling
+        YieldThread();
+#  if defined(XP_WIN)
+      } else if (!aWaitCondition) {
+        // WaitForMutex failed and we are not waiting for a condition to
+        // exit from PullState call.
+        return;
+      }
+#  endif  // defined(XP_WIN)
+    }  // End: Scope for WaitForMutex
+    // Yield the thread while polling
+    YieldThread();
+  }  // while (!true)
+}
+#endif    // defined(MOZ_WIDGET_ANDROID)
+
+void VRManager::PushState(bool aNotifyCond) {
+  if (!mExternalShmem) {
+    return;
+  }
+#if defined(MOZ_WIDGET_ANDROID)
+  if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) ==
+      0) {
+    memcpy((void*)&(mExternalShmem->geckoState), (void*)&mBrowserState,
+           sizeof(VRBrowserState));
+    if (aNotifyCond) {
+      pthread_cond_signal((pthread_cond_t*)&(mExternalShmem->geckoCond));
+    }
+    pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex));
+  }
+#else
+  bool status = true;
+#  if defined(XP_WIN)
+  WaitForMutex lock(mMutex);
+  status = lock.GetStatus();
+#  endif  // defined(XP_WIN)
+  if (status) {
+    mExternalShmem->geckoGenerationA++;
+    memcpy((void*)&(mExternalShmem->geckoState), (void*)&mBrowserState,
+           sizeof(VRBrowserState));
+    mExternalShmem->geckoGenerationB++;
+  }
+#endif    // defined(MOZ_WIDGET_ANDROID)
+}
+
+void VRManager::Destroy() {
+  if (mState == VRManagerState::Disabled) {
+    return;
+  }
+  Shutdown();
+  StopTasks();
+  mState = VRManagerState::Disabled;
+}
+
+void VRManager::Shutdown() {
+  if (mState == VRManagerState::Disabled || mState == VRManagerState::Idle) {
+    return;
+  }
+
+  if (mDisplayInfo.mDisplayState.shutdown) {
+    // Shutdown was requested by VR Service, so we must throttle
+    // as requested by the VR Service
+    TimeStamp now = TimeStamp::Now();
+    mEarliestRestartTime =
+        now + TimeDuration::FromMilliseconds(
+                  (double)mDisplayInfo.mDisplayState.minRestartInterval);
+  }
+
+  StopAllHaptics();
+  StopPresentation();
+  CancelCurrentSubmitTask();
+  ShutdownSubmitThread();
+  mDisplayInfo.Clear();
+  mEnumerationCompleted = false;
+
+  if (mState == VRManagerState::Enumeration) {
+    // Ensure that enumeration promises are resolved
+    DispatchVRDisplayInfoUpdate();
+  }
+
+#if !defined(MOZ_WIDGET_ANDROID)
+  mServiceHost->StopService();
+#endif
+  mState = VRManagerState::Idle;
+
+  // We will close Shmem in the DTOR to avoid
+  // mSubmitThread is still running but its shmem
+  // has been released.
+}
+
+void VRManager::CheckWatchDog() {
+  /**
+   * We will trigger a new frame immediately after a successful frame
+   * texture submission.  If content fails to call VRDisplay.submitFrame
+   * after dom.vr.display.rafMaxDuration milliseconds has elapsed since the
+   * last VRDisplay.requestAnimationFrame, we act as a "watchdog" and
+   * kick-off a new VRDisplay.requestAnimationFrame to avoid a render loop
+   * stall and to give content a chance to recover.
+   *
+   * If the lower level VR platform API's are rejecting submitted frames,
+   * such as when the Oculus "Health and Safety Warning" is displayed,
+   * we will not kick off the next frame immediately after
+   * VRDisplay.submitFrame as it would result in an unthrottled render loop
+   * that would free run at potentially extreme frame rates.  To ensure that
+   * content has a chance to resume its presentation when the frames are
+   * accepted once again, we rely on this "watchdog" to act as a VR refresh
+   * driver cycling at a rate defined by dom.vr.display.rafMaxDuration.
+   *
+   * This number must be larger than the slowest expected frame time during
+   * normal VR presentation, but small enough not to break content that
+   * makes assumptions of reasonably minimal VSync rate.
+   *
+   * The slowest expected refresh rate for a VR display currently is an
+   * Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz.
+   * A dom.vr.display.rafMaxDuration value of 50 milliseconds results in a
+   * 20hz rate, which avoids inadvertent triggering of the watchdog during
+   * Oculus ASW even if every second frame is dropped.
+   */
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  bool bShouldStartFrame = false;
+
+  // If content fails to call VRDisplay.submitFrame, we must eventually
+  // time-out and trigger a new frame.
+  TimeStamp lastFrameStart =
+      mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
+  if (lastFrameStart.IsNull()) {
+    bShouldStartFrame = true;
+  } else {
+    TimeDuration duration = TimeStamp::Now() - lastFrameStart;
+    if (duration.ToMilliseconds() >
+        StaticPrefs::dom_vr_display_rafMaxDuration()) {
+      bShouldStartFrame = true;
+    }
+  }
+
+  if (bShouldStartFrame) {
+    StartFrame();
+  }
+}
+
+void VRManager::ExpireNavigationTransition() {
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  if (!mVRNavigationTransitionEnd.IsNull() &&
+      TimeStamp::Now() > mVRNavigationTransitionEnd) {
+    mBrowserState.navigationTransitionActive = false;
+  }
+}
+
+void VRManager::UpdateHaptics(double aDeltaTime) {
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  bool bNeedPush = false;
+  // Check for any haptic pulses that have ended and clear them
+  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
+    const VRHapticState& state = mBrowserState.hapticState[i];
+    if (state.inputFrameID == 0) {
+      // Nothing in this slot
+      continue;
+    }
+    mHapticPulseRemaining[i] -= aDeltaTime;
+    if (mHapticPulseRemaining[i] <= 0.0f) {
+      // The pulse has finished
+      ClearHapticSlot(i);
+      bNeedPush = true;
+    }
+  }
+  if (bNeedPush) {
+    PushState();
+  }
+}
+
+void VRManager::ClearHapticSlot(size_t aSlot) {
+  MOZ_ASSERT(aSlot < mozilla::ArrayLength(mBrowserState.hapticState));
+  memset(&mBrowserState.hapticState[aSlot], 0, sizeof(VRHapticState));
+  mHapticPulseRemaining[aSlot] = 0.0f;
+  if (aSlot < mHapticPromises.Length() && mHapticPromises[aSlot]) {
+    NotifyVibrateHapticCompleted(*(mHapticPromises[aSlot]));
+    mHapticPromises[aSlot] = nullptr;
+  }
+}
+
+void VRManager::ShutdownSubmitThread() {
+  if (mSubmitThread) {
+    mSubmitThread->Shutdown();
+    mSubmitThread = nullptr;
+  }
+}
+
+void VRManager::StartPresentation() {
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  if (mBrowserState.presentationActive) {
+    return;
+  }
+  mTelemetry.Clear();
+  mTelemetry.mPresentationStart = TimeStamp::Now();
+
+  // Indicate that we are ready to start immersive mode
+  mBrowserState.presentationActive = true;
+  mBrowserState.layerState[0].type = VRLayerType::LayerType_Stereo_Immersive;
+  PushState();
+
+  mDisplayInfo.mDisplayState.lastSubmittedFrameId = 0;
+  if (mDisplayInfo.mDisplayState.reportsDroppedFrames) {
+    mTelemetry.mLastDroppedFrameCount =
+        mDisplayInfo.mDisplayState.droppedFrameCount;
+  }
+
+  mLastSubmittedFrameId = 0;
+  mLastStartedFrame = 0;
+}
+
+void VRManager::StopPresentation() {
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  if (!mBrowserState.presentationActive) {
+    return;
+  }
+
+  // Indicate that we have stopped immersive mode
+  mBrowserState.presentationActive = false;
+  memset(mBrowserState.layerState, 0,
+         sizeof(VRLayerState) * mozilla::ArrayLength(mBrowserState.layerState));
+
+  PushState(true);
+
+  Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount;
+  Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount;
+  int viewIn = 0;
+
+  if (mDisplayInfo.mDisplayState.eightCC ==
+      GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) {
+    // Oculus Desktop API
+    timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS;
+    droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS;
+    viewIn = 1;
+  } else if (mDisplayInfo.mDisplayState.eightCC ==
+             GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) {
+    // OpenVR API
+    timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR;
+    droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR;
+    viewIn = 2;
+  }
+
+  if (viewIn) {
+    const TimeDuration duration =
+        TimeStamp::Now() - mTelemetry.mPresentationStart;
+    Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, viewIn);
+    Telemetry::Accumulate(timeSpentID, duration.ToMilliseconds());
+    const uint32_t droppedFramesPerSec =
+        (uint32_t)((double)(mDisplayInfo.mDisplayState.droppedFrameCount -
+                            mTelemetry.mLastDroppedFrameCount) /
+                   duration.ToSeconds());
+    Telemetry::Accumulate(droppedFramesID, droppedFramesPerSec);
   }
 }
 
 bool VRManager::IsPresenting() {
-  for (const auto& manager : mManagers) {
-    if (manager->GetIsPresenting()) {
-      return true;
+  if (mExternalShmem) {
+    return mDisplayInfo.mPresentingGroups != 0;
+  }
+  return false;
+}
+
+void VRManager::SetGroupMask(uint32_t aGroupMask) {
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  mDisplayInfo.mGroupMask = aGroupMask;
+}
+
+void VRManager::SubmitFrame(VRLayerParent* aLayer,
+                            const layers::SurfaceDescriptor& aTexture,
+                            uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
+                            const gfx::Rect& aRightEyeRect) {
+  if (mState != VRManagerState::Active) {
+    return;
+  }
+  MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
+  if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
+    // Suppress layers hidden by the group mask
+    return;
+  }
+
+  // Ensure that we only accept the first SubmitFrame call per RAF cycle.
+  if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
+    return;
+  }
+
+  /**
+   * Do not queue more submit frames until the last submitted frame is
+   * already processed and the new WebGL texture is ready.
+   */
+  if (mLastSubmittedFrameId > 0 &&
+      mLastSubmittedFrameId !=
+          mDisplayInfo.mDisplayState.lastSubmittedFrameId) {
+    mLastStartedFrame = 0;
+    return;
+  }
+
+  mLastSubmittedFrameId = aFrameId;
+
+  mFrameStarted = false;
+
+  RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<
+      StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t,
+      StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>(
+      "gfx::VRManager::SubmitFrameInternal", this,
+      &VRManager::SubmitFrameInternal, aTexture, aFrameId, aLeftEyeRect,
+      aRightEyeRect);
+
+  if (!mCurrentSubmitTask) {
+    mCurrentSubmitTask = task;
+#if !defined(MOZ_WIDGET_ANDROID)
+    if (!mSubmitThread) {
+      mSubmitThread = new VRThread(NS_LITERAL_CSTRING("VR_SubmitFrame"));
+    }
+    mSubmitThread->Start();
+    mSubmitThread->PostTask(task.forget());
+#else
+    CompositorThreadHolder::Loop()->PostTask(task.forget());
+#endif  // defined(MOZ_WIDGET_ANDROID)
+  }
+}
+
+bool VRManager::SubmitFrame(const layers::SurfaceDescriptor& aTexture,
+                            uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
+                            const gfx::Rect& aRightEyeRect) {
+  if (mState != VRManagerState::Active) {
+    return false;
+  }
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
+  MOZ_ASSERT(mBrowserState.layerState[0].type ==
+             VRLayerType::LayerType_Stereo_Immersive);
+  VRLayer_Stereo_Immersive& layer =
+      mBrowserState.layerState[0].layer_stereo_immersive;
+
+  switch (aTexture.type()) {
+#  if defined(XP_WIN)
+    case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
+      const SurfaceDescriptorD3D10& surf =
+          aTexture.get_SurfaceDescriptorD3D10();
+      layer.textureType =
+          VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor;
+      layer.textureHandle = (void*)surf.handle();
+      layer.textureSize.width = surf.size().width;
+      layer.textureSize.height = surf.size().height;
+    } break;
+#  elif defined(XP_MACOSX)
+    case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
+      // MacIOSurface ptr can't be fetched or used at different threads.
+      // Both of fetching and using this MacIOSurface are at the VRService
+      // thread.
+      const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface();
+      layer.textureType = VRLayerTextureType::LayerTextureType_MacIOSurface;
+      layer.textureHandle = desc.surfaceId();
+      RefPtr<MacIOSurface> surf =
+          MacIOSurface::LookupSurface(desc.surfaceId(), desc.scaleFactor(),
+                                      !desc.isOpaque(), desc.yUVColorSpace());
+      if (surf) {
+        layer.textureSize.width = surf->GetDevicePixelWidth();
+        layer.textureSize.height = surf->GetDevicePixelHeight();
+      }
+    } break;
+#  elif defined(MOZ_WIDGET_ANDROID)
+    case SurfaceDescriptor::TSurfaceTextureDescriptor: {
+      const SurfaceTextureDescriptor& desc =
+          aTexture.get_SurfaceTextureDescriptor();
+      java::GeckoSurfaceTexture::LocalRef surfaceTexture =
+          java::GeckoSurfaceTexture::Lookup(desc.handle());
+      if (!surfaceTexture) {
+        NS_WARNING("VRManager::SubmitFrame failed to get a SurfaceTexture");
+        return false;
+      }
+      layer.textureType =
+          VRLayerTextureType::LayerTextureType_GeckoSurfaceTexture;
+      layer.textureHandle = desc.handle();
+      layer.textureSize.width = desc.size().width;
+      layer.textureSize.height = desc.size().height;
+    } break;
+#  endif
+    default: {
+      MOZ_ASSERT(false);
+      return false;
     }
   }
 
+  layer.frameId = aFrameId;
+  layer.inputFrameId =
+      mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames]
+          .inputFrameID;
+
+  layer.leftEyeRect.x = aLeftEyeRect.x;
+  layer.leftEyeRect.y = aLeftEyeRect.y;
+  layer.leftEyeRect.width = aLeftEyeRect.width;
+  layer.leftEyeRect.height = aLeftEyeRect.height;
+  layer.rightEyeRect.x = aRightEyeRect.x;
+  layer.rightEyeRect.y = aRightEyeRect.y;
+  layer.rightEyeRect.width = aRightEyeRect.width;
+  layer.rightEyeRect.height = aRightEyeRect.height;
+
+  PushState(true);
+
+  PullState([&]() {
+    return (mDisplayInfo.mDisplayState.lastSubmittedFrameId >= aFrameId) ||
+           mDisplayInfo.mDisplayState.suppressFrames ||
+           !mDisplayInfo.mDisplayState.isConnected;
+  });
+
+  if (mDisplayInfo.mDisplayState.suppressFrames ||
+      !mDisplayInfo.mDisplayState.isConnected) {
+    // External implementation wants to supress frames, service has shut
+    // down or hardware has been disconnected.
+    return false;
+  }
+
+  return mDisplayInfo.mDisplayState.lastSubmittedFrameSuccessful;
+#else
+  MOZ_ASSERT(false);  // Not implmented for this platform
   return false;
+#endif
+}
+
+void VRManager::SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
+                                    uint64_t aFrameId,
+                                    const gfx::Rect& aLeftEyeRect,
+                                    const gfx::Rect& aRightEyeRect) {
+#if !defined(MOZ_WIDGET_ANDROID)
+  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
+#endif  // !defined(MOZ_WIDGET_ANDROID)
+  AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplayExternal", OTHER);
+
+  {  // scope lock
+    MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
+
+    if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) {
+      mCurrentSubmitTask = nullptr;
+      return;
+    }
+    mCurrentSubmitTask = nullptr;
+  }
+
+#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
+
+  /**
+   * Trigger the next VSync immediately after we are successfully
+   * submitting frames.  As SubmitFrame is responsible for throttling
+   * the render loop, if we don't successfully call it, we shouldn't trigger
+   * StartFrame immediately, as it will run unbounded.
+   * If StartFrame is not called here due to SubmitFrame failing, the
+   * fallback "watchdog" code in VRManager::NotifyVSync() will cause
+   * frames to continue at a lower refresh rate until frame submission
+   * succeeds again.
+   */
+  MessageLoop* loop = CompositorThreadHolder::Loop();
+
+  loop->PostTask(NewRunnableMethod("gfx::VRManager::StartFrame", this,
+                                   &VRManager::StartFrame));
+#endif
+}
+
+void VRManager::CancelCurrentSubmitTask() {
+  MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
+  if (mCurrentSubmitTask) {
+    mCurrentSubmitTask->Cancel();
+    mCurrentSubmitTask = nullptr;
+  }
 }
 
 }  // namespace gfx
 }  // namespace mozilla
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -2,121 +2,168 @@
 /* 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 GFX_VR_MANAGER_H
 #define GFX_VR_MANAGER_H
 
-#include "nsRefPtrHashtable.h"
 #include "nsTArray.h"
-#include "nsTHashtable.h"
-#include "nsDataHashtable.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/TimeStamp.h"
 #include "gfxVR.h"
 
 class nsITimer;
 namespace mozilla {
-namespace layers {
-class TextureHost;
-}
 namespace gfx {
-
 class VRLayerParent;
 class VRManagerParent;
-class VRDisplayHost;
-#if !defined(MOZ_WIDGET_ANDROID)
-class VRService;
-#endif
-class VRSystemManagerPuppet;
-class VRSystemManagerExternal;
+class VRServiceHost;
+class VRThread;
+
+enum class VRManagerState : uint32_t {
+  Disabled,  // All VRManager activity is stopped
+  Idle,  // No VR hardware has been activated, but background tasks are running
+  Enumeration,  // Waiting for enumeration and startup of VR hardware
+  Active        // VR hardware is active
+};
 
 class VRManager {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRManager)
 
  public:
   static void ManagerInit();
   static VRManager* Get();
 
   void AddVRManagerParent(VRManagerParent* aVRManagerParent);
   void RemoveVRManagerParent(VRManagerParent* aVRManagerParent);
 
   void NotifyVsync(const TimeStamp& aVsyncTimestamp);
-  void NotifyVRVsync(const uint32_t& aDisplayID);
+
   void RefreshVRDisplays(bool aMustDispatch = false);
-  void RefreshVRControllers();
-  void ScanForControllers();
-  void RemoveControllers();
-  template <class T>
-  void NotifyGamepadChange(uint32_t aIndex, const T& aInfo);
-  RefPtr<gfx::VRDisplayHost> GetDisplay(const uint32_t& aDisplayID);
-  void GetVRDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayInfo);
-  RefPtr<gfx::VRControllerHost> GetController(const uint32_t& aControllerID);
-  void GetVRControllerInfo(nsTArray<VRControllerInfo>& aControllerInfo);
-  void CreateVRTestSystem();
-  VRSystemManagerPuppet* GetPuppetManager();
-  VRSystemManagerExternal* GetExternalManager();
+  void StopAllHaptics();
 
   void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                      double aIntensity, double aDuration,
                      const VRManagerPromise& aPromise);
   void StopVibrateHaptic(uint32_t aControllerIdx);
   void NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise);
-  void DispatchSubmitFrameResult(uint32_t aDisplayID,
-                                 const VRSubmitFrameResultInfo& aResult);
   void StartVRNavigation(const uint32_t& aDisplayID);
   void StopVRNavigation(const uint32_t& aDisplayID,
                         const TimeDuration& aTimeout);
-
   void Shutdown();
+#if !defined(MOZ_WIDGET_ANDROID)
+  bool RunPuppet(const InfallibleTArray<uint64_t>& aBuffer,
+                 VRManagerParent* aManagerParent);
+  void ResetPuppet(VRManagerParent* aManagerParent);
+#endif
+  void AddLayer(VRLayerParent* aLayer);
+  void RemoveLayer(VRLayerParent* aLayer);
+  void SetGroupMask(uint32_t aGroupMask);
+  void SubmitFrame(VRLayerParent* aLayer,
+                   const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
+                   const gfx::Rect& aLeftEyeRect,
+                   const gfx::Rect& aRightEyeRect);
   bool IsPresenting();
 
- protected:
+ private:
   VRManager();
   ~VRManager();
-
- private:
   void Destroy();
-  void Init();
   void StartTasks();
   void StopTasks();
   static void TaskTimerCallback(nsITimer* aTimer, void* aClosure);
   void RunTasks();
   void Run1msTasks(double aDeltaTime);
   void Run10msTasks();
   void Run100msTasks();
   uint32_t GetOptimalTaskInterval();
+  void PullState(const std::function<bool()>& aWaitCondition = nullptr);
+  void PushState(const bool aNotifyCond = false);
+  static uint32_t AllocateDisplayID();
 
   void DispatchVRDisplayInfoUpdate();
   void UpdateRequestedDevices();
   void EnumerateVRDisplays();
   void CheckForInactiveTimeout();
+#if !defined(MOZ_WIDGET_ANDROID)
+  void CheckForPuppetCompletion();
+#endif
+  void CheckForShutdown();
+  void CheckWatchDog();
+  void ExpireNavigationTransition();
+  void OpenShmem();
+  void CloseShmem();
+  void UpdateHaptics(double aDeltaTime);
+  void ClearHapticSlot(size_t aSlot);
+  void StartFrame();
+  void ShutdownSubmitThread();
+  void StartPresentation();
+  void StopPresentation();
+  void CancelCurrentSubmitTask();
 
+  void SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture,
+                           uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect);
+  bool SubmitFrame(const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
+                   const gfx::Rect& aLeftEyeRect,
+                   const gfx::Rect& aRightEyeRect);
+
+  Atomic<VRManagerState> mState;
   typedef nsTHashtable<nsRefPtrHashKey<VRManagerParent>> VRManagerParentSet;
   VRManagerParentSet mVRManagerParents;
+#if !defined(MOZ_WIDGET_ANDROID)
+  VRManagerParentSet mManagerParentsWaitingForPuppetReset;
+  RefPtr<VRManagerParent> mManagerParentRunningPuppet;
+#endif
+  // Weak reference to mLayers entries are cleared in
+  // VRLayerParent destructor
+  nsTArray<VRLayerParent*> mLayers;
 
-  typedef nsTArray<RefPtr<VRSystemManager>> VRSystemManagerArray;
-  VRSystemManagerArray mManagers;
-  nsTArray<uint32_t> mVRDisplayIDs;
-  nsTArray<uint32_t> mVRControllerIDs;
-
-  Atomic<bool> mInitialized;
-
-  TimeStamp mLastControllerEnumerationTime;
   TimeStamp mLastDisplayEnumerationTime;
   TimeStamp mLastActiveTime;
   TimeStamp mLastTickTime;
+  TimeStamp mEarliestRestartTime;
+  TimeStamp mVRNavigationTransitionEnd;
+  TimeStamp mLastFrameStart[kVRMaxLatencyFrames];
   double mAccumulator100ms;
-  RefPtr<VRSystemManagerPuppet> mPuppetManager;
-  RefPtr<VRSystemManagerExternal> mExternalManager;
   bool mVRDisplaysRequested;
   bool mVRDisplaysRequestedNonFocus;
   bool mVRControllersRequested;
-  bool mVRServiceStarted;
+  bool mFrameStarted;
+  volatile VRExternalShmem* mExternalShmem;
   uint32_t mTaskInterval;
   RefPtr<nsITimer> mTaskTimer;
+  mozilla::Monitor mCurrentSubmitTaskMonitor;
+  RefPtr<CancelableRunnable> mCurrentSubmitTask;
+  uint64_t mLastSubmittedFrameId;
+  uint64_t mLastStartedFrame;
+  bool mEnumerationCompleted;
+#if defined(XP_MACOSX)
+  int mShmemFD;
+#elif defined(XP_WIN)
+  base::ProcessHandle mShmemFile;
+  HANDLE mMutex;
+#endif
+#if !defined(MOZ_WIDGET_ANDROID)
+  bool mVRProcessEnabled;
+  RefPtr<VRServiceHost> mServiceHost;
+#endif
+
+  static Atomic<uint32_t> sDisplayBase;
+  RefPtr<VRThread> mSubmitThread;
+  VRTelemetry mTelemetry;
+  nsTArray<UniquePtr<VRManagerPromise>> mHapticPromises;
+  // Duration of haptic pulse time remaining (milliseconds)
+  double mHapticPulseRemaining[kVRHapticsMaxCount];
+
+  VRDisplayInfo mDisplayInfo;
+  VRDisplayInfo mLastUpdateDisplayInfo;
+  VRBrowserState mBrowserState;
+  VRHMDSensorState mLastSensorState;
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // GFX_VR_MANAGER_H
new file mode 100644
--- /dev/null
+++ b/gfx/vr/VRPuppetCommandBuffer.cpp
@@ -0,0 +1,512 @@
+/* -*- 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 "VRPuppetCommandBuffer.h"
+#include "prthread.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+namespace gfx {
+
+static StaticRefPtr<VRPuppetCommandBuffer> sVRPuppetCommandBufferSingleton;
+
+VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() {
+  if (sVRPuppetCommandBufferSingleton == nullptr) {
+    sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer();
+    ClearOnShutdown(&sVRPuppetCommandBufferSingleton);
+  }
+  return *sVRPuppetCommandBufferSingleton;
+}
+
+VRPuppetCommandBuffer::VRPuppetCommandBuffer()
+    : mMutex("VRPuppetCommandBuffer::mMutex") {
+  MOZ_COUNT_CTOR(VRPuppetCommandBuffer);
+  MOZ_ASSERT(sVRPuppetCommandBufferSingleton == nullptr);
+  Reset();
+}
+
+VRPuppetCommandBuffer::~VRPuppetCommandBuffer() {
+  MOZ_COUNT_DTOR(VRPuppetCommandBuffer);
+}
+
+void VRPuppetCommandBuffer::Submit(const InfallibleTArray<uint64_t>& aBuffer) {
+  MutexAutoLock lock(mMutex);
+  mBuffer.AppendElements(aBuffer);
+  mEnded = false;
+  mEndedWithTimeout = false;
+}
+
+bool VRPuppetCommandBuffer::HasEnded() {
+  MutexAutoLock lock(mMutex);
+  return mEnded;
+}
+
+void VRPuppetCommandBuffer::Reset() {
+  MutexAutoLock lock(mMutex);
+  memset(&mPendingState, 0, sizeof(VRSystemState));
+  memset(&mCommittedState, 0, sizeof(VRSystemState));
+  for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
+       iControllerIdx++) {
+    for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+      mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
+      mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
+    }
+  }
+  mDataOffset = 0;
+  mPresentationRequested = false;
+  mFrameSubmitted = false;
+  mFrameAccepted = false;
+  mTimeoutDuration = 10.0f;
+  mWaitRemaining = 0.0f;
+  mBlockedTime = 0.0f;
+  mTimerElapsed = 0.0f;
+  mEnded = true;
+  mEndedWithTimeout = false;
+  mLastRunTimestamp = TimeStamp();
+  mTimerSamples.Clear();
+  mBuffer.Clear();
+}
+
+bool VRPuppetCommandBuffer::RunCommand(uint64_t aCommand, double aDeltaTime) {
+  /**
+   * Run a single command.  If the command is blocking on a state change and
+   * can't be executed, return false.
+   *
+   * VRPuppetCommandBuffer::RunCommand is only called by
+   *VRPuppetCommandBuffer::Run(), which is already holding the mutex.
+   *
+   * Note that VRPuppetCommandBuffer::RunCommand may potentially be called >1000
+   *times per frame. It might not hurt to add an assert here, but we should
+   *avoid adding code which may potentially malloc (eg string handling) even for
+   *debug builds here.  This function will need to be reasonably fast, even in
+   *debug builds which will be using it during tests.
+   **/
+  switch ((VRPuppet_Command)(aCommand & 0xff00000000000000)) {
+    case VRPuppet_Command::VRPuppet_End:
+      CompleteTest(false);
+      break;
+    case VRPuppet_Command::VRPuppet_ClearAll:
+      memset(&mPendingState, 0, sizeof(VRSystemState));
+      memset(&mCommittedState, 0, sizeof(VRSystemState));
+      mPresentationRequested = false;
+      mFrameSubmitted = false;
+      mFrameAccepted = false;
+      break;
+    case VRPuppet_Command::VRPuppet_ClearController: {
+      uint8_t controllerIdx = aCommand & 0x00000000000000ff;
+      if (controllerIdx < kVRControllerMaxCount) {
+        mPendingState.controllerState[controllerIdx].Clear();
+      }
+    } break;
+    case VRPuppet_Command::VRPuppet_Timeout:
+      mTimeoutDuration = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
+      break;
+    case VRPuppet_Command::VRPuppet_Wait:
+      if (mWaitRemaining == 0.0f) {
+        mWaitRemaining = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
+        // Wait timer started, block
+        return false;
+      }
+      mWaitRemaining -= aDeltaTime;
+      if (mWaitRemaining > 0.0f) {
+        // Wait timer still running, block
+        return false;
+      }
+      // Wait timer has elapsed, unblock
+      mWaitRemaining = 0.0f;
+      break;
+    case VRPuppet_Command::VRPuppet_WaitSubmit:
+      if (!mFrameSubmitted) {
+        return false;
+      }
+      break;
+    case VRPuppet_Command::VRPuppet_CaptureFrame:
+      // TODO - Capture the frame and record the output (Bug 1555180)
+      break;
+    case VRPuppet_Command::VRPuppet_AcknowledgeFrame:
+      mFrameSubmitted = false;
+      mFrameAccepted = true;
+      break;
+    case VRPuppet_Command::VRPuppet_RejectFrame:
+      mFrameSubmitted = false;
+      mFrameAccepted = false;
+      break;
+    case VRPuppet_Command::VRPuppet_WaitPresentationStart:
+      if (!mPresentationRequested) {
+        return false;
+      }
+      break;
+    case VRPuppet_Command::VRPuppet_WaitPresentationEnd:
+      if (mPresentationRequested) {
+        return false;
+      }
+      break;
+    case VRPuppet_Command::VRPuppet_WaitHapticIntensity: {
+      // 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v)
+      uint8_t iControllerIdx = (aCommand & 0x0000ff0000000000) >> 40;
+      if (iControllerIdx >= kVRControllerMaxCount) {
+        // Puppet test is broken, ensure it fails
+        return false;
+      }
+      uint8_t iHapticIdx = (aCommand & 0x000000ff00000000) >> 32;
+      if (iHapticIdx >= kNumPuppetHaptics) {
+        // Puppet test is broken, ensure it fails
+        return false;
+      }
+      uint32_t iHapticIntensity =
+          aCommand & 0x00000000ffffffff;  // interpreted as 16.16 fixed point
+
+      SimulateHaptics(aDeltaTime);
+      uint64_t iCurrentIntensity =
+          round(mHapticPulseIntensity[iControllerIdx][iHapticIdx] *
+                (1 << 16));  // convert to 16.16 fixed point
+      if (iCurrentIntensity > 0xffffffff) {
+        iCurrentIntensity = 0xffffffff;
+      }
+      if (iCurrentIntensity != iHapticIntensity) {
+        return false;
+      }
+    } break;
+
+    case VRPuppet_Command::VRPuppet_StartTimer:
+      mTimerElapsed = 0.0f;
+      break;
+    case VRPuppet_Command::VRPuppet_StopTimer:
+      mTimerSamples.AppendElements(mTimerElapsed);
+      // TODO - Return the timer samples to Javascript once the command buffer
+      // is complete (Bug 1555182)
+      break;
+
+    case VRPuppet_Command::VRPuppet_UpdateDisplay:
+      mDataOffset = (uint8_t*)&mPendingState.displayState -
+                    (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
+      break;
+    case VRPuppet_Command::VRPuppet_UpdateSensor:
+      mDataOffset = (uint8_t*)&mPendingState.sensorState -
+                    (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
+      break;
+    case VRPuppet_Command::VRPuppet_UpdateControllers:
+      mDataOffset = (uint8_t*)&mPendingState
+                        .controllerState[aCommand & 0x00000000000000ff] -
+                    (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
+      break;
+    case VRPuppet_Command::VRPuppet_Commit:
+      memcpy(&mCommittedState, &mPendingState, sizeof(VRSystemState));
+      break;
+
+    case VRPuppet_Command::VRPuppet_Data7:
+      WriteData((aCommand & 0x00ff000000000000) >> 48);
+      MOZ_FALLTHROUGH;
+      // Purposefully, no break
+    case VRPuppet_Command::VRPuppet_Data6:
+      WriteData((aCommand & 0x0000ff0000000000) >> 40);
+      MOZ_FALLTHROUGH;
+      // Purposefully, no break
+    case VRPuppet_Command::VRPuppet_Data5:
+      WriteData((aCommand & 0x000000ff00000000) >> 32);
+      MOZ_FALLTHROUGH;
+      // Purposefully, no break
+    case VRPuppet_Command::VRPuppet_Data4:
+      WriteData((aCommand & 0x00000000ff000000) >> 24);
+      MOZ_FALLTHROUGH;
+      // Purposefully, no break
+    case VRPuppet_Command::VRPuppet_Data3:
+      WriteData((aCommand & 0x0000000000ff0000) >> 16);
+      MOZ_FALLTHROUGH;
+      // Purposefully, no break
+    case VRPuppet_Command::VRPuppet_Data2:
+      WriteData((aCommand & 0x000000000000ff00) >> 8);
+      MOZ_FALLTHROUGH;
+      // Purposefully, no break
+    case VRPuppet_Command::VRPuppet_Data1:
+      WriteData(aCommand & 0x00000000000000ff);
+      break;
+  }
+  return true;
+}
+
+void VRPuppetCommandBuffer::WriteData(uint8_t aData) {
+  if (mDataOffset && mDataOffset < sizeof(VRSystemState)) {
+    ((uint8_t*)&mPendingState)[mDataOffset++] = aData;
+  }
+}
+
+void VRPuppetCommandBuffer::Run() {
+  MutexAutoLock lock(mMutex);
+  TimeStamp now = TimeStamp::Now();
+  double deltaTime = 0.0f;
+  if (!mLastRunTimestamp.IsNull()) {
+    deltaTime = (now - mLastRunTimestamp).ToSeconds();
+  }
+  mLastRunTimestamp = now;
+  mTimerElapsed += deltaTime;
+  size_t transactionLength = 0;
+  while (transactionLength < mBuffer.Length() && !mEnded) {
+    if (RunCommand(mBuffer[transactionLength], deltaTime)) {
+      mBlockedTime = 0.0f;
+      transactionLength++;
+    } else {
+      mBlockedTime += deltaTime;
+      if (mBlockedTime > mTimeoutDuration) {
+        CompleteTest(true);
+      }
+      // If a command is blocked, we don't increment transactionLength,
+      // allowing the command to be retried on the next cycle
+      break;
+    }
+  }
+  mBuffer.RemoveElementsAt(0, transactionLength);
+}
+
+void VRPuppetCommandBuffer::Run(VRSystemState& aState) {
+  Run();
+  // We don't want to stomp over some members
+  bool bEnumerationCompleted = aState.enumerationCompleted;
+  bool bShutdown = aState.displayState.shutdown;
+  uint32_t minRestartInterval = aState.displayState.minRestartInterval;
+
+  // Overwrite it all
+  memcpy(&aState, &mCommittedState, sizeof(VRSystemState));
+
+  // Restore the members
+  aState.enumerationCompleted = bEnumerationCompleted;
+  aState.displayState.shutdown = bShutdown;
+  aState.displayState.minRestartInterval = minRestartInterval;
+}
+
+void VRPuppetCommandBuffer::StartPresentation() {
+  mPresentationRequested = true;
+  Run();
+}
+
+void VRPuppetCommandBuffer::StopPresentation() {
+  mPresentationRequested = false;
+  Run();
+}
+
+bool VRPuppetCommandBuffer::SubmitFrame() {
+  // Emulate blocking behavior of various XR API's as
+  // described by puppet script
+  mFrameSubmitted = true;
+  mFrameAccepted = false;
+  while (true) {
+    Run();
+    if (!mFrameSubmitted || mEnded) {
+      break;
+    }
+    PR_Sleep(PR_INTERVAL_NO_WAIT);  // Yield
+  }
+
+  return mFrameAccepted;
+}
+
+void VRPuppetCommandBuffer::VibrateHaptic(uint32_t aControllerIdx,
+                                          uint32_t aHapticIndex,
+                                          float aIntensity, float aDuration) {
+  if (aHapticIndex >= kNumPuppetHaptics ||
+      aControllerIdx >= kVRControllerMaxCount) {
+    return;
+  }
+
+  // We must Run() before and after updating haptic state to avoid script
+  // deadlocks
+  // The deadlocks would be caused by scripts that include two
+  // VRPuppet_WaitHapticIntensity commands.  If
+  // VRPuppetCommandBuffer::VibrateHaptic() is called twice without advancing
+  // through the command buffer with VRPuppetCommandBuffer::Run() in between,
+  // the first VRPuppet_WaitHapticInensity may not see the transient value that
+  // it is waiting for, thus blocking forever and deadlocking the script.
+  Run();
+  mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
+  mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
+  Run();
+}
+
+void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx) {
+  if (aControllerIdx >= kVRControllerMaxCount) {
+    return;
+  }
+  // We must Run() before and after updating haptic state to avoid script
+  // deadlocks
+  Run();
+  for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+    mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
+    mHapticPulseIntensity[aControllerIdx][iHaptic] = 0.0f;
+  }
+  Run();
+}
+
+void VRPuppetCommandBuffer::StopAllHaptics() {
+  // We must Run() before and after updating haptic state to avoid script
+  // deadlocks
+  Run();
+  for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
+       iControllerIdx++) {
+    for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+      mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
+      mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
+    }
+  }
+  Run();
+}
+
+void VRPuppetCommandBuffer::SimulateHaptics(double aDeltaTime) {
+  for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
+       iControllerIdx++) {
+    for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+      if (mHapticPulseIntensity[iControllerIdx][iHaptic] > 0.0f) {
+        mHapticPulseRemaining[iControllerIdx][iHaptic] -= aDeltaTime;
+        if (mHapticPulseRemaining[iControllerIdx][iHaptic] <= 0.0f) {
+          mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
+          mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
+        }
+      }
+    }
+  }
+}
+
+void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut) {
+  mEndedWithTimeout = aTimedOut;
+  mEnded = true;
+}
+
+/**
+ *  Generates a sequence of VRPuppet_Data# commands, as described
+ *  in VRPuppetCommandBuffer.h, to encode the changes to be made to
+ *  a "destination" structure to match the "source" structure.
+ *  As the commands are encoded, the destination structure is updated
+ *  to match the source.
+ *
+ * @param aBuffer
+ *     The buffer in which the commands will be appended.
+ * @param aSrcStart
+ *     Byte pointer to the start of the structure that
+ *     will be copied from.
+ * @param aDstStart
+ *     Byte pointer to the start of the structure that
+ *     will be copied to.
+ * @param aLength
+ *     Length of the structure that will be copied.
+ * @param aUpdateCommand
+ *     VRPuppet_... command indicating which structure is being
+ *     copied:
+ *     VRPuppet_Command::VRPuppet_UpdateDisplay:
+ *         A single VRDisplayState struct
+ *     VRPuppet_Command::VRPuppet_UpdateSensor:
+ *         A single VRHMDSensorState struct
+ *     VRPuppet_Command::VRPuppet_UpdateControllers:
+ *         An array of VRControllerState structs with a
+ *         count of kVRControllerMaxCount
+ */
+void VRPuppetCommandBuffer::EncodeStruct(nsTArray<uint64_t>& aBuffer,
+                                         uint8_t* aSrcStart, uint8_t* aDstStart,
+                                         size_t aLength,
+                                         VRPuppet_Command aUpdateCommand) {
+  // Naive implementation, but will not be executed in realtime, so will not
+  // affect test timer results. Could be improved to avoid unaligned reads and
+  // to use SSE.
+
+  // Pointer to source byte being compared+copied
+  uint8_t* src = aSrcStart;
+
+  // Pointer to destination byte being compared+copied
+  uint8_t* dst = aDstStart;
+
+  // Number of bytes packed into bufData
+  uint8_t bufLen = 0;
+
+  // 64-bits to be interpreted as up to 7 separate bytes
+  // This will form the lower 56 bits of the command
+  uint64_t bufData = 0;
+
+  // purgebuffer takes the bytes stored in bufData and generates a VRPuppet
+  // command representing those bytes as "VRPuppet Data".
+  // VRPUppet_Data1 encodes 1 byte
+  // VRPuppet_Data2 encodes 2 bytes
+  // and so on, until..
+  // VRPuppet_Data7 encodes 7 bytes
+  // This command is appended to aBuffer, then bufLen and bufData are reset
+  auto purgeBuffer = [&]() {
+    // Only emit a command if there are data bytes in bufData
+    if (bufLen > 0) {
+      MOZ_ASSERT(bufLen <= 7);
+      uint64_t command = (uint64_t)VRPuppet_Command::VRPuppet_Data1;
+      command += ((uint64_t)VRPuppet_Command::VRPuppet_Data2 -
+                  (uint64_t)VRPuppet_Command::VRPuppet_Data1) *
+                 (bufLen - 1);
+      command |= bufData;
+      aBuffer.AppendElement(command);
+      bufLen = 0;
+      bufData = 0;
+    }
+  };
+
+  // Loop through the bytes of the structs.
+  // While copying the struct at aSrcStart to aDstStart,
+  // the differences are encoded as VRPuppet commands and
+  // appended to aBuffer.
+  for (size_t i = 0; i < aLength; i++) {
+    if (*src != *dst) {
+      // This byte is different
+
+      // Copy the byte to the destination
+      *dst = *src;
+
+      if (bufLen == 0) {
+        // This is the start of a new span of changed bytes
+
+        // Output a command to specify the offset of the
+        // span.
+        aBuffer.AppendElement((uint64_t)aUpdateCommand + i);
+
+        // Store this first byte in bufData.
+        // We will batch up to 7 bytes in one VRPuppet_DataXX
+        // command, so we won't emit it yet.
+        bufLen = 1;
+        bufData = *src;
+      } else if (bufLen <= 6) {
+        // This is the continuation of a span of changed bytes.
+        // There is room to add more bytes to bufData.
+
+        // Store the next byte in bufData.
+        // We will batch up to 7 bytes in one VRPuppet_DataXX
+        // command, so we won't emit it yet.
+        bufData = (bufData << 8) | *src;
+        bufLen++;
+      } else {
+        MOZ_ASSERT(bufLen == 7);
+        // This is the continuation of a span of changed bytes.
+        // There are already 7 bytes in bufData, so we must emit
+        // the VRPuppet_Data7 command for the prior bytes before
+        // starting a new command.
+        aBuffer.AppendElement((uint64_t)VRPuppet_Command::VRPuppet_Data7 +
+                              bufData);
+
+        // Store this byte to be included in the next VRPuppet_DataXX
+        // command.
+        bufLen = 1;
+        bufData = *src;
+      }
+    } else {
+      // This byte is the same.
+      // If there are bytes in bufData, the span has now ended and we must
+      // emit a VRPuppet_DataXX command for the accumulated bytes.
+      // purgeBuffer will not emit any commands if there are no bytes
+      // accumulated.
+      purgeBuffer();
+    }
+    // Advance to the next source and destination byte.
+    ++src;
+    ++dst;
+  }
+  // In the event that the very last byte of the structs differ, we must
+  // ensure that the accumulated bytes are emitted as a VRPuppet_DataXX
+  // command.
+  purgeBuffer();
+}
+
+}  // namespace gfx
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/vr/VRPuppetCommandBuffer.h
@@ -0,0 +1,235 @@
+/* -*- 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 GFX_VR_SERVICE_VRPUPPETCOMMANDBUFFER_H
+#define GFX_VR_SERVICE_VRPUPPETCOMMANDBUFFER_H
+
+#include <inttypes.h>
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+#include "moz_external_vr.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace gfx {
+
+/**
+ * A Puppet Device command buffer consists of a stream of 64-bit
+ * commands.
+ * The first 8 bits identifies the command and informs format of
+ * the remaining 56 bits.
+ *
+ * These commands will be streamed into a buffer until
+ * VRPuppet_End (0x0000000000000000) has been received.
+ *
+ * When VRPuppet_End is received, this command buffer will
+ * be executed asynchronously, collecting the timer results
+ * requested.
+ *
+ * In addition to the effects seen through the WebXR/VR API, tests
+ * can get further feedback from the Puppet device.
+ * Command buffers can be constructed such that when expected states
+ * are not reached, the timeout timer will expire.
+ * Data recorded with timer commands is returned when the command
+ * buffer is completed, to validate against accepted ranges and to
+ * quantify performance regressions.
+ * Images submitted to the Puppet display are rendered with the
+ * 2d browser output in order to enable reftests to validate
+ * output against reference images.
+ *
+ * The state of the virtual puppet device is expressed to the VR service
+ * in the same manner as physical devices -- using the VRDisplayState,
+ * VRHMDSensorState and VRControllerState structures.
+ *
+ * By enabling partial updates of these structures, the command buffer
+ * size is reduced to the values that change each frame.  This enables
+ * realtime capture of a session, with physical hardware, for
+ * replay in automated tests and benchmarks.
+ *
+ * All updates to the state structures are atomically updated in the
+ * VR session thread, triggered by VRPuppet_Commit (0x1500000000000000).
+ *
+ * Command buffers are expected to be serialized to a human readable,
+ * ascii format if stored on disk.  The binary representation is not
+ * guaranteed to be consistent between versions or target platforms.
+ * They should be re-constructed with the VRServiceTest DOM api at
+ * runtime.
+ *
+ * The command set:
+ *
+ * 0x0000000000000000 - VRPuppet_End()
+ * - End of stream, resolve promise returned by VRServiceTest::Play()
+ *
+ * 0x0100000000000000 - VRPuppet_ClearAll()
+ * - Clear all structs
+ *
+ * 0x02000000000000nn - VRPuppet_ClearController(n)
+ * - Clear a single controller struct
+ *
+ * 0x03000000nnnnnnnn - VRPuppet_Timeout(n)
+ * - Reset the timeout timer to n milliseconds
+ * - Initially the timeout timer starts at 10 seconds
+ *
+ * 0x04000000nnnnnnnn - VRPuppet_Wait(n)
+ * - Wait n milliseconds before advancing to next command
+ *
+ * 0x0500000000000000 - VRPuppet_WaitSubmit()
+ * - Wait until a frame has been submitted before advancing to the next command
+ *
+ * 0x0600000000000000 - VRPuppet_WaitPresentationStart()
+ * - Wait until a presentation becomes active
+ *
+ * 0x0700000000000000 - VRPuppet_WaitPresentationEnd()
+ * - Wait until a presentation ends
+ *
+ * 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v)
+ * - Wait until controller at index c's haptic actuator at index h reaches value
+ * v.
+ * - v is a 16.16 fixed point value, with 1.0f being the highest intensity and
+ * 0.0f indicating that the haptic actuator is not running
+ *
+ * 0x0900000000000000 - VRPuppet_CaptureFrame()
+ * - Captures the submitted frame.  Must later call
+ *   VRPuppet_AcknowledgeFrame or VRPuppet_RejectFrame
+ *   to unblock
+ *
+ * 0x0900000000000000 - VRPuppet_AcknowledgeFrame()
+ * - Acknowledge the submitted frame, unblocking the Submit call.
+ *
+ * 0x0a00000000000000 - VRPuppet_RejectFrame()
+ * - Reject the submitted frame, unblocking the Submit call.
+ *
+ * 0x0b00000000000000 - VRPuppet_StartTimer()
+ * - Starts the timer
+ *
+ * 0x0c00000000000000 - VRPuppet_StopTimer()
+ * - Stops the timer, the elapsed duration is recorded for access after the end
+ *   of stream
+ *
+ * 0x0d000000aaaaaaaa - VRPuppet_UpdateDisplay(a)
+ * - Start writing data to the VRDisplayState struct, at offset a
+ *
+ * 0x0e000000aaaaaaaa - VRPuppet_UpdateSensor(a)
+ * - Start writing data to the VRHMDSensorState struct, at offset a
+ *
+ * 0x0f000000aaaaaaaa - VRPuppet_UpdateControllers(a)
+ * - Start writing data to the VRControllerState array, at offset a
+ *
+ * 0x100000000000000 - VRPuppet_Commit
+ * - Atomically commit the VRPuppet_Data updates to VRDisplayState,
+ *   VRHMDSensorState and VRControllerState.
+ *
+ * 0xf0000000000000dd - VRPuppet_Data(d)
+ * - 1 byte of data
+ *
+ * 0xf10000000000dddd - VRPuppet_Data(d)
+ * - 2 bytes of data
+ *
+ * 0xf200000000dddddd - VRPuppet_Data(d)
+ * - 3 bytes of data
+ *
+ * 0xf3000000dddddddd - VRPuppet_Data(d)
+ * - 4 bytes of data
+ *
+ * 0xf40000dddddddddd - VRPuppet_Data(d)
+ * - 5 bytes of data
+ *
+ * 0xf500dddddddddddd - VRPuppet_Data(d)
+ * - 6 bytes of data
+ *
+ * 0xf6dddddddddddddd - VRPuppet_Data(d)
+ * - 7 bytes of data
+ *
+ */
+enum class VRPuppet_Command : uint64_t {
+  VRPuppet_End = 0x0000000000000000,
+  VRPuppet_ClearAll = 0x0100000000000000,
+  VRPuppet_ClearController = 0x0200000000000000,
+  VRPuppet_Timeout = 0x0300000000000000,
+  VRPuppet_Wait = 0x0400000000000000,
+  VRPuppet_WaitSubmit = 0x0500000000000000,
+  VRPuppet_WaitPresentationStart = 0x0600000000000000,
+  VRPuppet_WaitPresentationEnd = 0x0700000000000000,
+  VRPuppet_WaitHapticIntensity = 0x0800000000000000,
+  VRPuppet_CaptureFrame = 0x0900000000000000,
+  VRPuppet_AcknowledgeFrame = 0x0a00000000000000,
+  VRPuppet_RejectFrame = 0x0b00000000000000,
+  VRPuppet_StartTimer = 0x0c00000000000000,
+  VRPuppet_StopTimer = 0x0d00000000000000,
+  VRPuppet_UpdateDisplay = 0x0e00000000000000,
+  VRPuppet_UpdateSensor = 0x0f00000000000000,
+  VRPuppet_UpdateControllers = 0x1000000000000000,
+  VRPuppet_Commit = 0x1100000000000000,
+  VRPuppet_Data1 = 0xf000000000000000,
+  VRPuppet_Data2 = 0xf100000000000000,
+  VRPuppet_Data3 = 0xf200000000000000,
+  VRPuppet_Data4 = 0xf300000000000000,
+  VRPuppet_Data5 = 0xf400000000000000,
+  VRPuppet_Data6 = 0xf500000000000000,
+  VRPuppet_Data7 = 0xf600000000000000,
+};
+
+static const int kNumPuppetHaptics = 8;
+
+class VRPuppetCommandBuffer {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRPuppetCommandBuffer)
+  static VRPuppetCommandBuffer& Get();
+
+  // Interface to VRTestSystem
+  void Submit(const InfallibleTArray<uint64_t>& aBuffer);
+  void Reset();
+  bool HasEnded();
+
+  // Interface to PuppetSession
+  void Run(VRSystemState& aState);
+  void StartPresentation();
+  void StopPresentation();
+  bool SubmitFrame();
+  void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                     float aIntensity, float aDuration);
+  void StopVibrateHaptic(uint32_t aControllerIdx);
+  void StopAllHaptics();
+
+  static void EncodeStruct(nsTArray<uint64_t>& aBuffer, uint8_t* aSrcStart,
+                           uint8_t* aDstStart, size_t aLength,
+                           gfx::VRPuppet_Command aUpdateCommand);
+
+ private:
+  VRPuppetCommandBuffer();
+  ~VRPuppetCommandBuffer();
+  void Run();
+  bool RunCommand(uint64_t aCommand, double aDeltaTime);
+  void WriteData(uint8_t aData);
+  void SimulateHaptics(double aDeltaTime);
+  void CompleteTest(bool aTimedOut);
+  nsTArray<uint64_t> mBuffer;
+  mozilla::Mutex mMutex;
+  VRSystemState mPendingState;
+  VRSystemState mCommittedState;
+  double mHapticPulseRemaining[kVRControllerMaxCount][kNumPuppetHaptics];
+  float mHapticPulseIntensity[kVRControllerMaxCount][kNumPuppetHaptics];
+
+  size_t mDataOffset;
+  bool mPresentationRequested;
+  bool mFrameSubmitted;
+  bool mFrameAccepted;
+  double mTimeoutDuration;  // Seconds
+  double mWaitRemaining;    // Seconds
+  double mBlockedTime;      // Seconds
+  double mTimerElapsed;     // Seconds
+  TimeStamp mLastRunTimestamp;
+
+  // Test Results:
+  bool mEnded;
+  bool mEndedWithTimeout;
+  nsTArray<double> mTimerSamples;
+};
+
+}  // namespace gfx
+}  // namespace mozilla
+
+#endif  // GFX_VR_SERVICE_VRPUPPETCOMMANDBUFFER_H
new file mode 100644
--- /dev/null
+++ b/gfx/vr/VRServiceHost.cpp
@@ -0,0 +1,226 @@
+/* -*- 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 "VRServiceHost.h"
+#include "VRGPUChild.h"
+#include "VRManager.h"
+#include "VRPuppetCommandBuffer.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPrefs.h"
+#include "mozilla/gfx/GPUParent.h"
+#include "service/VRService.h"
+
+namespace mozilla {
+namespace gfx {
+
+static StaticRefPtr<VRServiceHost> sVRServiceHostSingleton;
+
+/* static */
+void VRServiceHost::Init(bool aEnableVRProcess) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sVRServiceHostSingleton == nullptr) {
+    sVRServiceHostSingleton = new VRServiceHost(aEnableVRProcess);
+    ClearOnShutdown(&sVRServiceHostSingleton);
+  }
+}
+
+/* static */
+VRServiceHost* VRServiceHost::Get() {
+  MOZ_ASSERT(sVRServiceHostSingleton != nullptr);
+  return sVRServiceHostSingleton;
+}
+
+VRServiceHost::VRServiceHost(bool aEnableVRProcess)
+    : mPuppetActive(false)
+#if !defined(MOZ_WIDGET_ANDROID)
+      ,
+      mVRService(nullptr),
+      mVRProcessEnabled(aEnableVRProcess),
+      mVRProcessStarted(false),
+      mVRServiceRequested(false)
+
+#endif
+{
+  MOZ_COUNT_CTOR(VRServiceHost);
+}
+
+VRServiceHost::~VRServiceHost() {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_DTOR(VRServiceHost);
+}
+
+void VRServiceHost::StartService() {
+  mVRServiceRequested = true;
+  if (mVRProcessEnabled) {
+    // VRService in the VR process
+    RefreshVRProcess();
+  } else if (mVRService) {
+    // VRService in the GPU process if enabled, or
+    // the parent process if the GPU process is not enabled.
+    mVRService->Start();
+  }
+}
+
+void VRServiceHost::StopService() {
+  mVRServiceRequested = false;
+  if (mVRProcessEnabled) {
+    // VRService in the VR process
+    RefreshVRProcess();
+  } else if (mVRService) {
+    // VRService in the GPU process if enabled, or
+    // the parent process if the GPU process is not enabled.
+    mVRService->Stop();
+  }
+}
+
+void VRServiceHost::Shutdown() {
+  PuppetReset();
+  StopService();
+  mVRService = nullptr;
+}
+
+void VRServiceHost::Refresh() {
+  if (mVRService) {
+    mVRService->Refresh();
+  }
+}
+
+#if !defined(MOZ_WIDGET_ANDROID)
+
+void VRServiceHost::CreateService(volatile VRExternalShmem* aShmem) {
+  MOZ_ASSERT(!mVRProcessEnabled);
+  mVRService = VRService::Create(aShmem);
+}
+
+bool VRServiceHost::NeedVRProcess() {
+  if (!mVRProcessEnabled) {
+    return false;
+  }
+  if (mVRServiceRequested) {
+    return true;
+  }
+  if (mPuppetActive) {
+    return true;
+  }
+  return false;
+}
+
+void VRServiceHost::RefreshVRProcess() {
+  // Start or stop the VR process if needed
+  if (NeedVRProcess()) {
+    if (!mVRProcessStarted) {
+      CreateVRProcess();
+    }
+  } else {
+    if (mVRProcessStarted) {
+      ShutdownVRProcess();
+    }
+  }
+}
+
+void VRServiceHost::CreateVRProcess() {
+  // This is only allowed to run in the main thread of the GPU process
+  if (!XRE_IsGPUProcess()) {
+    return;
+  }
+  // Forward this to the main thread if not already there
+  if (!NS_IsMainThread()) {
+    RefPtr<Runnable> task = NS_NewRunnableFunction(
+        "VRServiceHost::CreateVRProcess",
+        []() -> void { VRServiceHost::Get()->CreateVRProcess(); });
+    NS_DispatchToMainThread(task.forget());
+    return;
+  }
+  if (mVRProcessStarted) {
+    return;
+  }
+
+  mVRProcessStarted = true;
+  // Using PGPU channel to tell the main process
+  // to create the VR process.
+  gfx::GPUParent* gpu = GPUParent::GetSingleton();
+  MOZ_ASSERT(gpu);
+  Unused << gpu->SendCreateVRProcess();
+}
+
+void VRServiceHost::ShutdownVRProcess() {
+  // This is only allowed to run in the main thread of the GPU process
+  if (!XRE_IsGPUProcess()) {
+    return;
+  }
+  // Forward this to the main thread if not already there
+  if (!NS_IsMainThread()) {
+    RefPtr<Runnable> task = NS_NewRunnableFunction(
+        "VRServiceHost::ShutdownVRProcess",
+        []() -> void { VRServiceHost::Get()->ShutdownVRProcess(); });
+    NS_DispatchToMainThread(task.forget());
+    return;
+  }
+  if (VRGPUChild::IsCreated()) {
+    VRGPUChild* vrGPUChild = VRGPUChild::Get();
+    vrGPUChild->SendStopVRService();
+    if (!vrGPUChild->IsClosed()) {
+      vrGPUChild->Close();
+    }
+    VRGPUChild::Shutdown();
+  }
+  if (!mVRProcessStarted) {
+    return;
+  }
+  // Using PGPU channel to tell the main process
+  // to shutdown VR process.
+  gfx::GPUParent* gpu = GPUParent::GetSingleton();
+  MOZ_ASSERT(gpu);
+  Unused << gpu->SendShutdownVRProcess();
+  mVRProcessStarted = false;
+}
+
+#endif  // !defined(MOZ_WIDGET_ANDROID)
+
+void VRServiceHost::PuppetSubmit(const InfallibleTArray<uint64_t>& aBuffer) {
+  if (mVRProcessEnabled) {
+    mPuppetActive = true;
+    // TODO - Implement VR puppet support for VR process (Bug 1555188)
+    MOZ_ASSERT(false);  // Not implemented
+  } else {
+    VRPuppetCommandBuffer::Get().Submit(aBuffer);
+  }
+}
+
+void VRServiceHost::PuppetReset() {
+  if (mVRProcessEnabled) {
+    mPuppetActive = false;
+    if (!mVRProcessStarted) {
+      // Process is stopped, so puppet state is already clear
+      return;
+    }
+    // TODO - Implement VR puppet support for VR process (Bug 1555188)
+    MOZ_ASSERT(false);  // Not implemented
+  } else {
+    VRPuppetCommandBuffer::Get().Reset();
+  }
+}
+
+bool VRServiceHost::PuppetHasEnded() {
+  if (mVRProcessEnabled) {
+    if (!mVRProcessStarted) {
+      // The VR process will be kept alive as long
+      // as there is a queue in the puppet command
+      // buffer.  If the process is stopped, we can
+      // infer that the queue has been cleared and
+      // puppet state is reset.
+      return true;
+    }
+    // TODO - Implement VR puppet support for VR process (Bug 1555188)
+    MOZ_ASSERT(false);  // Not implemented
+    return false;
+  }
+  return VRPuppetCommandBuffer::Get().HasEnded();
+}
+
+}  // namespace gfx
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/vr/VRServiceHost.h
@@ -0,0 +1,56 @@
+/* -*- 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 GFX_VR_SERVICE_HOST_H
+#define GFX_VR_SERVICE_HOST_H
+
+namespace mozilla {
+namespace gfx {
+
+class VRService;
+
+class VRServiceHost {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRServiceHost)
+ public:
+  static void Init(bool aEnableVRProcess);
+  static VRServiceHost* Get();
+
+  void Refresh();
+  void StartService();
+  void StopService();
+  void Shutdown();
+#if !defined(MOZ_WIDGET_ANDROID)
+  void CreateService(volatile VRExternalShmem* aShmem);
+#endif
+
+  void PuppetSubmit(const InfallibleTArray<uint64_t>& aBuffer);
+  void PuppetReset();
+  bool PuppetHasEnded();
+
+ protected:
+ private:
+  explicit VRServiceHost(bool aEnableVRProcess);
+  ~VRServiceHost();
+
+  bool mPuppetActive;
+#if !defined(MOZ_WIDGET_ANDROID)
+  void RefreshVRProcess();
+  bool NeedVRProcess();
+  void CreateVRProcess();
+  void ShutdownVRProcess();
+
+  RefPtr<VRService> mVRService;
+  bool mVRProcessEnabled;
+  bool mVRProcessStarted;
+  bool mVRServiceRequested;
+
+#endif
+};
+
+}  // namespace gfx
+}  // namespace mozilla
+
+#endif  // GFX_VR_SERVICE_HOST_H
--- a/gfx/vr/external_api/moz_external_vr.h
+++ b/gfx/vr/external_api/moz_external_vr.h
@@ -7,23 +7,26 @@
 #ifndef GFX_VR_EXTERNAL_API_H
 #define GFX_VR_EXTERNAL_API_H
 
 #define GFX_VR_EIGHTCC(c1, c2, c3, c4, c5, c6, c7, c8)                  \
   ((uint64_t)(c1) << 56 | (uint64_t)(c2) << 48 | (uint64_t)(c3) << 40 | \
    (uint64_t)(c4) << 32 | (uint64_t)(c5) << 24 | (uint64_t)(c6) << 16 | \
    (uint64_t)(c7) << 8 | (uint64_t)(c8))
 
-#include <stddef.h>
-#include <stdint.h>
-#include <type_traits>
-
 #ifdef MOZILLA_INTERNAL_API
+#  define __STDC_WANT_LIB_EXT1__ 1
+// __STDC_WANT_LIB_EXT1__ required for memcpy_s
+#  include <stdlib.h>
+#  include <string.h>
 #  include "mozilla/TypedEnumBits.h"
 #  include "mozilla/gfx/2D.h"
+#  include <stddef.h>
+#  include <stdint.h>
+#  include <type_traits>
 #endif  // MOZILLA_INTERNAL_API
 
 #if defined(__ANDROID__)
 #  include <pthread.h>
 #endif  // defined(__ANDROID__)
 
 namespace mozilla {
 #ifdef MOZILLA_INTERNAL_API
@@ -279,16 +282,20 @@ struct VRDisplayState {
   // We can't use a Matrix4x4 here unless we ensure it's a POD type
   float sittingToStandingTransform[16];
   uint64_t lastSubmittedFrameId;
   bool lastSubmittedFrameSuccessful;
   uint32_t presentingGeneration;
   // Telemetry
   bool reportsDroppedFrames;
   uint64_t droppedFrameCount;
+
+#ifdef MOZILLA_INTERNAL_API
+  void Clear() { memset(this, 0, sizeof(VRDisplayState)); }
+#endif
 };
 
 struct VRControllerState {
   char controllerName[kVRControllerNameMaxLen];
 #ifdef MOZILLA_INTERNAL_API
   dom::GamepadHand hand;
 #else
   ControllerHand hand;
@@ -306,16 +313,19 @@ struct VRControllerState {
 #ifdef MOZILLA_INTERNAL_API
   dom::GamepadCapabilityFlags flags;
 #else
   ControllerCapabilityFlags flags;
 #endif
   VRPose pose;
   bool isPositionValid;
   bool isOrientationValid;
+#ifdef MOZILLA_INTERNAL_API
+  void Clear() { memset(this, 0, sizeof(VRControllerState)); }
+#endif
 };
 
 struct VRLayerEyeRect {
   float x;
   float y;
   float width;
   float height;
 };
@@ -378,16 +388,20 @@ struct VRHapticState {
 struct VRBrowserState {
 #if defined(__ANDROID__)
   bool shutdown;
 #endif  // defined(__ANDROID__)
   bool presentationActive;
   bool navigationTransitionActive;
   VRLayerState layerState[kVRLayerMaxCount];
   VRHapticState hapticState[kVRHapticsMaxCount];
+
+#ifdef MOZILLA_INTERNAL_API
+  void Clear() { memset(this, 0, sizeof(VRBrowserState)); }
+#endif
 };
 
 struct VRSystemState {
   bool enumerationCompleted;
   VRDisplayState displayState;
   VRHMDSensorState sensorState;
   VRControllerState controllerState[kVRControllerMaxCount];
 };
@@ -412,16 +426,38 @@ struct VRExternalShmem {
   int64_t servoGenerationA;
 #endif  // !defined(__ANDROID__)
   VRBrowserState geckoState;
   VRBrowserState servoState;
 #if !defined(__ANDROID__)
   int64_t geckoGenerationB;
   int64_t servoGenerationB;
 #endif  // !defined(__ANDROID__)
+#ifdef MOZILLA_INTERNAL_API
+  void Clear() volatile {
+/**
+ * When possible we do a memset_s, which is explicitly safe for
+ * the volatile, POD struct.  A memset may be optimized out by
+ * the compiler and will fail to compile due to volatile keyword
+ * propagation.
+ *
+ * A loop-based fallback is provided in case the toolchain does
+ * not include STDC_LIB_EXT1 for memset_s.
+ */
+#  ifdef __STDC_LIB_EXT1__
+    memset_s((void*)this, sizeof(VRExternalShmem), 0, sizeof(VRExternalShmem));
+#  else
+    size_t remaining = sizeof(VRExternalShmem);
+    volatile unsigned char* d = (volatile unsigned char*)this;
+    while (remaining--) {
+      *d++ = 0;
+    }
+#  endif
+  }
+#endif
 };
 
 // As we are memcpy'ing VRExternalShmem and its members around, it must be a POD
 // type
 static_assert(std::is_pod<VRExternalShmem>::value,
               "VRExternalShmem must be a POD type.");
 
 }  // namespace gfx
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.cpp
@@ -2,94 +2,24 @@
 /* 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 <math.h>
 
 #include "gfxVR.h"
-#include "mozilla/dom/GamepadEventTypes.h"
-#include "mozilla/dom/GamepadBinding.h"
-#include "VRDisplayHost.h"
 
 #ifndef M_PI
 #  define M_PI 3.14159265358979323846
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
-Atomic<uint32_t> VRSystemManager::sDisplayBase(0);
-Atomic<uint32_t> VRSystemManager::sControllerBase(0);
-
-/* static */
-uint32_t VRSystemManager::AllocateDisplayID() { return ++sDisplayBase; }
-
-/* static */
-uint32_t VRSystemManager::AllocateControllerID() { return ++sControllerBase; }
-
-/**
- * VRSystemManager::NotifyVsync must be called even when a WebVR site is
- * not active, in order to poll for respond to VR Platform API requests.
- * This should be called very often, ideally once per frame.
- * VRSystemManager::Refresh will not activate VR hardware or
- * initialize VR runtimes that have not already been activated.
- */
-void VRSystemManager::NotifyVSync() {
-  // VRDisplayHost::NotifyVSync may modify mVRDisplays, so we iterate
-  // through a local copy here.
-  nsTArray<RefPtr<VRDisplayHost>> displays;
-  GetHMDs(displays);
-  for (const auto& display : displays) {
-    display->NotifyVSync();
-  }
-
-  // Ensure that the controller state is updated at least
-  // on every 2d display VSync when not in a VR presentation.
-  if (!GetIsPresenting()) {
-    HandleInput();
-  }
-}
-
-void VRSystemManager::Run1msTasks(double aDeltaTime) {
-  // To be overridden by children
-}
-
-void VRSystemManager::Run10msTasks() {
-  // To be overridden by children
-}
-
-void VRSystemManager::Run100msTasks() {
-  // To be overridden by children
-}
-
-/**
- * VRSystemManager::GetHMDs must not be called unless
- * VRSystemManager::ShouldInhibitEnumeration is called
- * on all VRSystemManager instances and they all return
- * false.
- *
- * This is used to ensure that VR devices that can be
- * enumerated by multiple API's are only enumerated by
- * one API.
- *
- * GetHMDs is called for the most specific API
- * (ie. Oculus SDK) first before calling GetHMDs on
- * more generic api's (ie. OpenVR) to ensure that a device
- * is accessed using the API most optimized for it.
- *
- * ShouldInhibitEnumeration may also be used to prevent
- * devices from jumping to other API's when they are
- * intentionally ignored, such as when responding to
- * requests by the VR platform to unload the libraries
- * for runtime software updates.
- */
-bool VRSystemManager::ShouldInhibitEnumeration() { return false; }
-
 Matrix4x4 VRFieldOfView::ConstructProjectionMatrix(float zNear, float zFar,
                                                    bool rightHanded) const {
   float upTan = tan(upDegrees * M_PI / 180.0);
   float downTan = tan(downDegrees * M_PI / 180.0);
   float leftTan = tan(leftDegrees * M_PI / 180.0);
   float rightTan = tan(rightDegrees * M_PI / 180.0);
 
   float handednessScale = rightHanded ? -1.0 : 1.0;
@@ -112,67 +42,16 @@ Matrix4x4 VRFieldOfView::ConstructProjec
   m[3 * 4 + 2] = (zFar * zNear) / (zNear - zFar);
 
   m[2 * 4 + 3] = handednessScale;
   m[3 * 4 + 3] = 0.0f;
 
   return mobj;
 }
 
-void VRSystemManager::AddGamepad(const VRControllerInfo& controllerInfo) {
-  dom::GamepadAdded a(
-      NS_ConvertUTF8toUTF16(controllerInfo.GetControllerName()),
-      controllerInfo.GetMappingType(), controllerInfo.GetHand(),
-      controllerInfo.GetDisplayID(), controllerInfo.GetNumButtons(),
-      controllerInfo.GetNumAxes(), controllerInfo.GetNumHaptics(), 0, 0);
-
-  VRManager* vm = VRManager::Get();
-  vm->NotifyGamepadChange<dom::GamepadAdded>(mControllerCount, a);
-}
-
-void VRSystemManager::RemoveGamepad(uint32_t aIndex) {
-  dom::GamepadRemoved a;
-
-  VRManager* vm = VRManager::Get();
-  vm->NotifyGamepadChange<dom::GamepadRemoved>(aIndex, a);
-}
-
-void VRSystemManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
-                                     bool aPressed, bool aTouched,
-                                     double aValue) {
-  dom::GamepadButtonInformation a(aButton, aValue, aPressed, aTouched);
-
-  VRManager* vm = VRManager::Get();
-  vm->NotifyGamepadChange<dom::GamepadButtonInformation>(aIndex, a);
-}
-
-void VRSystemManager::NewAxisMove(uint32_t aIndex, uint32_t aAxis,
-                                  double aValue) {
-  dom::GamepadAxisInformation a(aAxis, aValue);
-
-  VRManager* vm = VRManager::Get();
-  vm->NotifyGamepadChange<dom::GamepadAxisInformation>(aIndex, a);
-}
-
-void VRSystemManager::NewPoseState(uint32_t aIndex,
-                                   const dom::GamepadPoseState& aPose) {
-  dom::GamepadPoseInformation a(aPose);
-
-  VRManager* vm = VRManager::Get();
-  vm->NotifyGamepadChange<dom::GamepadPoseInformation>(aIndex, a);
-}
-
-void VRSystemManager::NewHandChangeEvent(uint32_t aIndex,
-                                         const dom::GamepadHand aHand) {
-  dom::GamepadHandInformation a(aHand);
-
-  VRManager* vm = VRManager::Get();
-  vm->NotifyGamepadChange<dom::GamepadHandInformation>(aIndex, a);
-}
-
 void VRHMDSensorState::CalcViewMatrices(
     const gfx::Matrix4x4* aHeadToEyeTransforms) {
   gfx::Matrix4x4 matHead;
   if (flags & VRDisplayCapabilityFlags::Cap_Orientation) {
     matHead.SetRotationFromQuaternion(
         gfx::Quaternion(pose.orientation[0], pose.orientation[1],
                         pose.orientation[2], pose.orientation[3]));
   }
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -12,74 +12,62 @@
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnumBits.h"
+#include <type_traits>
 
 namespace mozilla {
 namespace layers {
 class PTextureParent;
 }
 namespace dom {
 enum class GamepadMappingType : uint8_t;
 enum class GamepadHand : uint8_t;
-struct GamepadPoseState;
 }  // namespace dom
 namespace gfx {
 class VRLayerParent;
 class VRDisplayHost;
-class VRControllerHost;
 class VRManagerPromise;
 
 // The maximum number of frames of latency that we would expect before we
 // should give up applying pose prediction.
 // If latency is greater than one second, then the experience is not likely
 // to be corrected by pose prediction.  Setting this value too
 // high may result in unnecessary memory allocation.
 // As the current fastest refresh rate is 90hz, 100 is selected as a
 // conservative value.
 static const int kVRMaxLatencyFrames = 100;
 
-enum class VRDeviceType : uint16_t {
-  Oculus,
-  OpenVR,
-  OSVR,
-  GVR,
-  Puppet,
-  External,
-  NumVRDeviceTypes
-};
-
 enum class OpenVRControllerType : uint16_t {
   Vive,
   WMR,
   Knuckles,
   Cosmos,
   NumOpenVRControllerTypes
 };
 
 struct VRDisplayInfo {
   uint32_t mDisplayID;
-  VRDeviceType mType;
   uint32_t mPresentingGroups;
   uint32_t mGroupMask;
   uint64_t mFrameId;
   VRDisplayState mDisplayState;
   VRControllerState mControllerState[kVRControllerMaxCount];
 
   VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames];
+  void Clear() { memset(this, 0, sizeof(VRDisplayInfo)); }
   const VRHMDSensorState& GetSensorState() const {
     return mLastSensorState[mFrameId % kVRMaxLatencyFrames];
   }
 
-  VRDeviceType GetType() const { return mType; }
   uint32_t GetDisplayID() const { return mDisplayID; }
   const char* GetDisplayName() const { return mDisplayState.displayName; }
   VRDisplayCapabilityFlags GetCapabilities() const {
     return mDisplayState.capabilityFlags;
   }
 
   const IntSize SuggestedEyeResolution() const;
   const Point3D GetEyeTranslation(uint32_t whichEye) const;
@@ -97,63 +85,64 @@ struct VRDisplayInfo {
   bool operator==(const VRDisplayInfo& other) const {
     for (size_t i = 0; i < kVRMaxLatencyFrames; i++) {
       if (mLastSensorState[i] != other.mLastSensorState[i]) {
         return false;
       }
     }
     // Note that mDisplayState and mControllerState are asserted to be POD
     // types, so memcmp is safe
-    return mType == other.mType && mDisplayID == other.mDisplayID &&
+    return mDisplayID == other.mDisplayID &&
            memcmp(&mDisplayState, &other.mDisplayState,
                   sizeof(VRDisplayState)) == 0 &&
            memcmp(mControllerState, other.mControllerState,
                   sizeof(VRControllerState) * kVRControllerMaxCount) == 0 &&
            mPresentingGroups == other.mPresentingGroups &&
            mGroupMask == other.mGroupMask && mFrameId == other.mFrameId;
   }
 
   bool operator!=(const VRDisplayInfo& other) const {
     return !(*this == other);
   }
 };
 
+static_assert(std::is_pod<VRDisplayInfo>::value,
+              "VRDisplayInfo must be a POD type.");
+
 struct VRSubmitFrameResultInfo {
   VRSubmitFrameResultInfo()
       : mFormat(SurfaceFormat::UNKNOWN), mFrameNum(0), mWidth(0), mHeight(0) {}
 
   nsCString mBase64Image;
   SurfaceFormat mFormat;
   uint64_t mFrameNum;
   uint32_t mWidth;
   uint32_t mHeight;
 };
 
 struct VRControllerInfo {
-  VRDeviceType GetType() const { return mType; }
   uint32_t GetControllerID() const { return mControllerID; }
   const char* GetControllerName() const {
     return mControllerState.controllerName;
   }
   dom::GamepadMappingType GetMappingType() const { return mMappingType; }
   uint32_t GetDisplayID() const { return mDisplayID; }
   dom::GamepadHand GetHand() const { return mControllerState.hand; }
   uint32_t GetNumButtons() const { return mControllerState.numButtons; }
   uint32_t GetNumAxes() const { return mControllerState.numAxes; }
   uint32_t GetNumHaptics() const { return mControllerState.numHaptics; }
 
   uint32_t mControllerID;
-  VRDeviceType mType;
   dom::GamepadMappingType mMappingType;
   uint32_t mDisplayID;
   VRControllerState mControllerState;
   bool operator==(const VRControllerInfo& other) const {
     // Note that mControllerState is asserted to be a POD type, so memcmp is
     // safe
-    return mType == other.mType && mControllerID == other.mControllerID &&
+    return mControllerID == other.mControllerID &&
            memcmp(&mControllerState, &other.mControllerState,
                   sizeof(VRControllerState)) == 0 &&
            mMappingType == other.mMappingType && mDisplayID == other.mDisplayID;
   }
 
   bool operator!=(const VRControllerInfo& other) const {
     return !(*this == other);
   }
@@ -168,58 +157,12 @@ struct VRTelemetry {
   }
 
   bool IsLastDroppedFrameValid() { return (mLastDroppedFrameCount != -1); }
 
   TimeStamp mPresentationStart;
   int32_t mLastDroppedFrameCount;
 };
 
-class VRSystemManager {
- public:
-  static uint32_t AllocateDisplayID();
-  static uint32_t AllocateControllerID();
-
- protected:
-  static Atomic<uint32_t> sDisplayBase;
-  static Atomic<uint32_t> sControllerBase;
-
- public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRSystemManager)
-
-  virtual void Destroy() = 0;
-  virtual void Shutdown() = 0;
-  virtual void Enumerate() = 0;
-  virtual void NotifyVSync();
-  virtual void Run1msTasks(double aDeltaTime);
-  virtual void Run10msTasks();
-  virtual void Run100msTasks();
-  virtual bool ShouldInhibitEnumeration();
-  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) = 0;
-  virtual bool GetIsPresenting() = 0;
-  virtual void HandleInput() = 0;
-  virtual void GetControllers(
-      nsTArray<RefPtr<VRControllerHost>>& aControllerResult) = 0;
-  virtual void ScanForControllers() = 0;
-  virtual void RemoveControllers() = 0;
-  virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
-                             double aIntensity, double aDuration,
-                             const VRManagerPromise& aPromise) = 0;
-  virtual void StopVibrateHaptic(uint32_t aControllerIdx) = 0;
-  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
-                      bool aTouched, double aValue);
-  void NewAxisMove(uint32_t aIndex, uint32_t aAxis, double aValue);
-  void NewPoseState(uint32_t aIndex, const dom::GamepadPoseState& aPose);
-  void NewHandChangeEvent(uint32_t aIndex, const dom::GamepadHand aHand);
-  void AddGamepad(const VRControllerInfo& controllerInfo);
-  void RemoveGamepad(uint32_t aIndex);
-
- protected:
-  VRSystemManager() : mControllerCount(0) {}
-  virtual ~VRSystemManager() = default;
-
-  uint32_t mControllerCount;
-};
-
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif /* GFX_VR_H */
deleted file mode 100644
--- a/gfx/vr/gfxVRExternal.cpp
+++ /dev/null
@@ -1,929 +0,0 @@
-/* -*- 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 <math.h>
-
-#include "prlink.h"
-#include "prenv.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/StaticPrefs.h"
-
-#include "mozilla/gfx/Quaternion.h"
-
-#ifdef XP_WIN
-#  include "CompositorD3D11.h"
-#  include "TextureD3D11.h"
-static const char* kShmemName = "moz.gecko.vr_ext.0.0.1";
-#elif defined(XP_MACOSX)
-#  include "mozilla/gfx/MacIOSurface.h"
-#  include <sys/mman.h>
-#  include <sys/stat.h> /* For mode constants */
-#  include <fcntl.h>    /* For O_* constants */
-#  include <errno.h>
-static const char* kShmemName = "/moz.gecko.vr_ext.0.0.1";
-#elif defined(MOZ_WIDGET_ANDROID)
-#  include <string.h>
-#  include <pthread.h>
-#  include "GeckoVRManager.h"
-#endif  // defined(MOZ_WIDGET_ANDROID)
-
-#include "gfxVRExternal.h"
-#include "gfxVRMutex.h"
-#include "VRManagerParent.h"
-#include "VRManager.h"
-#include "VRThread.h"
-
-#include "nsServiceManagerUtils.h"
-#include "nsIScreenManager.h"
-
-#include "mozilla/dom/GamepadEventTypes.h"
-#include "mozilla/dom/GamepadBinding.h"
-#include "mozilla/Telemetry.h"
-
-#ifndef M_PI
-#  define M_PI 3.14159265358979323846
-#endif
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::gfx::impl;
-using namespace mozilla::layers;
-using namespace mozilla::dom;
-
-VRDisplayExternal::VRDisplayExternal(const VRDisplayState& aDisplayState)
-    : VRDisplayHost(VRDeviceType::External),
-      mHapticPulseRemaining{},
-      mBrowserState{},
-      mLastSensorState{} {
-  MOZ_COUNT_CTOR_INHERITED(VRDisplayExternal, VRDisplayHost);
-  mDisplayInfo.mDisplayState = aDisplayState;
-
-  // default to an identity quaternion
-  mLastSensorState.pose.orientation[3] = 1.0f;
-}
-
-VRDisplayExternal::~VRDisplayExternal() {
-  Destroy();
-  MOZ_COUNT_DTOR_INHERITED(VRDisplayExternal, VRDisplayHost);
-}
-
-void VRDisplayExternal::Destroy() {
-  StopAllHaptics();
-  StopPresentation();
-}
-
-void VRDisplayExternal::ZeroSensor() {}
-
-void VRDisplayExternal::Run1msTasks(double aDeltaTime) {
-  VRDisplayHost::Run1msTasks(aDeltaTime);
-  UpdateHaptics(aDeltaTime);
-}
-
-void VRDisplayExternal::Run10msTasks() {
-  VRDisplayHost::Run10msTasks();
-  ExpireNavigationTransition();
-  PullState();
-  PushState();
-
-  // 1ms tasks will always be run before
-  // the 10ms tasks, so no need to include
-  // them here as well.
-}
-
-void VRDisplayExternal::ExpireNavigationTransition() {
-  if (!mVRNavigationTransitionEnd.IsNull() &&
-      TimeStamp::Now() > mVRNavigationTransitionEnd) {
-    mBrowserState.navigationTransitionActive = false;
-  }
-}
-
-VRHMDSensorState& VRDisplayExternal::GetSensorState() {
-  return mLastSensorState;
-}
-
-void VRDisplayExternal::StartPresentation() {
-  if (mBrowserState.presentationActive) {
-    return;
-  }
-  mTelemetry.Clear();
-  mTelemetry.mPresentationStart = TimeStamp::Now();
-
-  // Indicate that we are ready to start immersive mode
-  mBrowserState.presentationActive = true;
-  mBrowserState.layerState[0].type = VRLayerType::LayerType_Stereo_Immersive;
-  PushState();
-
-  mDisplayInfo.mDisplayState.lastSubmittedFrameId = 0;
-  if (mDisplayInfo.mDisplayState.reportsDroppedFrames) {
-    mTelemetry.mLastDroppedFrameCount =
-        mDisplayInfo.mDisplayState.droppedFrameCount;
-  }
-
-#if defined(MOZ_WIDGET_ANDROID)
-  mLastSubmittedFrameId = 0;
-  mLastStartedFrame = 0;
-#endif
-}
-
-void VRDisplayExternal::StopPresentation() {
-  if (!mBrowserState.presentationActive) {
-    return;
-  }
-
-  // Indicate that we have stopped immersive mode
-  mBrowserState.presentationActive = false;
-  memset(mBrowserState.layerState, 0,
-         sizeof(VRLayerState) * mozilla::ArrayLength(mBrowserState.layerState));
-
-  PushState(true);
-
-  Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount;
-  Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount;
-  int viewIn = 0;
-
-  if (mDisplayInfo.mDisplayState.eightCC ==
-      GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) {
-    // Oculus Desktop API
-    timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS;
-    droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS;
-    viewIn = 1;
-  } else if (mDisplayInfo.mDisplayState.eightCC ==
-             GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) {
-    // OpenVR API
-    timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR;
-    droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR;
-    viewIn = 2;
-  }
-
-  if (viewIn) {
-    const TimeDuration duration =
-        TimeStamp::Now() - mTelemetry.mPresentationStart;
-    Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, viewIn);
-    Telemetry::Accumulate(timeSpentID, duration.ToMilliseconds());
-    const uint32_t droppedFramesPerSec =
-        (mDisplayInfo.mDisplayState.droppedFrameCount -
-         mTelemetry.mLastDroppedFrameCount) /
-        duration.ToSeconds();
-    Telemetry::Accumulate(droppedFramesID, droppedFramesPerSec);
-  }
-}
-
-void VRDisplayExternal::StartVRNavigation() {
-  mBrowserState.navigationTransitionActive = true;
-  mVRNavigationTransitionEnd = TimeStamp();
-  PushState();
-}
-
-void VRDisplayExternal::StopVRNavigation(const TimeDuration& aTimeout) {
-  if (aTimeout.ToMilliseconds() <= 0) {
-    mBrowserState.navigationTransitionActive = false;
-    mVRNavigationTransitionEnd = TimeStamp();
-    PushState();
-  }
-  mVRNavigationTransitionEnd = TimeStamp::Now() + aTimeout;
-}
-
-bool VRDisplayExternal::PopulateLayerTexture(
-    const layers::SurfaceDescriptor& aTexture, VRLayerTextureType* aTextureType,
-    VRLayerTextureHandle* aTextureHandle, IntSize_POD* aTextureSize) {
-  switch (aTexture.type()) {
-#if defined(XP_WIN)
-    case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
-      const SurfaceDescriptorD3D10& surf =
-          aTexture.get_SurfaceDescriptorD3D10();
-      *aTextureType =
-          VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor;
-      *aTextureHandle = (void*)surf.handle();
-      aTextureSize->width = surf.size().width;
-      aTextureSize->height = surf.size().height;
-      return true;
-    }
-#elif defined(XP_MACOSX)
-    case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
-      // MacIOSurface ptr can't be fetched or used at different threads.
-      // Both of fetching and using this MacIOSurface are at the VRService
-      // thread.
-      const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface();
-      *aTextureType = VRLayerTextureType::LayerTextureType_MacIOSurface;
-      *aTextureHandle = desc.surfaceId();
-      RefPtr<MacIOSurface> surf =
-          MacIOSurface::LookupSurface(desc.surfaceId(), desc.scaleFactor(),
-                                      !desc.isOpaque(), desc.yUVColorSpace());
-      if (surf) {
-        aTextureSize->width = surf->GetDevicePixelWidth();
-        aTextureSize->height = surf->GetDevicePixelHeight();
-      }
-      return true;
-    }
-#elif defined(MOZ_WIDGET_ANDROID)
-    case SurfaceDescriptor::TSurfaceTextureDescriptor: {
-      const SurfaceTextureDescriptor& desc =
-          aTexture.get_SurfaceTextureDescriptor();
-      java::GeckoSurfaceTexture::LocalRef surfaceTexture =
-          java::GeckoSurfaceTexture::Lookup(desc.handle());
-      if (!surfaceTexture) {
-        NS_WARNING("VRDisplayHost::SubmitFrame failed to get a SurfaceTexture");
-        return false;
-      }
-      *aTextureType = VRLayerTextureType::LayerTextureType_GeckoSurfaceTexture;
-      *aTextureHandle = desc.handle();
-      aTextureSize->width = desc.size().width;
-      aTextureSize->height = desc.size().height;
-      return true;
-    }
-#endif
-    default: {
-      MOZ_ASSERT(false);
-      return false;
-    }
-  }
-}
-
-bool VRDisplayExternal::SubmitFrame(const layers::SurfaceDescriptor& aTexture,
-                                    uint64_t aFrameId,
-                                    const gfx::Rect& aLeftEyeRect,
-                                    const gfx::Rect& aRightEyeRect) {
-  MOZ_ASSERT(mBrowserState.layerState[0].type ==
-             VRLayerType::LayerType_Stereo_Immersive);
-  VRLayer_Stereo_Immersive& layer =
-      mBrowserState.layerState[0].layer_stereo_immersive;
-  if (!PopulateLayerTexture(aTexture, &layer.textureType, &layer.textureHandle,
-                            &layer.textureSize)) {
-    return false;
-  }
-  layer.frameId = aFrameId;
-  layer.inputFrameId =
-      mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames]
-          .inputFrameID;
-
-  layer.leftEyeRect.x = aLeftEyeRect.x;
-  layer.leftEyeRect.y = aLeftEyeRect.y;
-  layer.leftEyeRect.width = aLeftEyeRect.width;
-  layer.leftEyeRect.height = aLeftEyeRect.height;
-  layer.rightEyeRect.x = aRightEyeRect.x;
-  layer.rightEyeRect.y = aRightEyeRect.y;
-  layer.rightEyeRect.width = aRightEyeRect.width;
-  layer.rightEyeRect.height = aRightEyeRect.height;
-
-  PushState(true);
-
-#if defined(MOZ_WIDGET_ANDROID)
-  PullState([&]() {
-    return (mDisplayInfo.mDisplayState.lastSubmittedFrameId >= aFrameId) ||
-           mDisplayInfo.mDisplayState.suppressFrames ||
-           !mDisplayInfo.mDisplayState.isConnected;
-  });
-
-  if (mDisplayInfo.mDisplayState.suppressFrames ||
-      !mDisplayInfo.mDisplayState.isConnected) {
-    // External implementation wants to supress frames, service has shut down or
-    // hardware has been disconnected.
-    return false;
-  }
-#else
-  while (mDisplayInfo.mDisplayState.lastSubmittedFrameId < aFrameId) {
-    if (PullState()) {
-      if (mDisplayInfo.mDisplayState.suppressFrames ||
-          !mDisplayInfo.mDisplayState.isConnected) {
-        // External implementation wants to supress frames, service has shut
-        // down or hardware has been disconnected.
-        return false;
-      }
-    }
-#  ifdef XP_WIN
-    Sleep(0);
-#  else
-    sleep(0);
-#  endif
-  }
-#endif  // defined(MOZ_WIDGET_ANDROID)
-
-  return mDisplayInfo.mDisplayState.lastSubmittedFrameSuccessful;
-}
-
-void VRDisplayExternal::VibrateHaptic(uint32_t aControllerIdx,
-                                      uint32_t aHapticIndex, double aIntensity,
-                                      double aDuration,
-                                      const VRManagerPromise& aPromise) {
-  TimeStamp now = TimeStamp::Now();
-  size_t bestSlotIndex = 0;
-  // Default to an empty slot, or the slot holding the oldest haptic pulse
-  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
-    const VRHapticState& state = mBrowserState.hapticState[i];
-    if (state.inputFrameID == 0) {
-      // Unused slot, use it
-      bestSlotIndex = i;
-      break;
-    }
-    if (mHapticPulseRemaining[i] < mHapticPulseRemaining[bestSlotIndex]) {
-      // If no empty slots are available, fall back to overriding
-      // the pulse which is ending soonest.
-      bestSlotIndex = i;
-    }
-  }
-  // Override the last pulse on the same actuator if present.
-  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
-    const VRHapticState& state = mBrowserState.hapticState[i];
-    if (state.inputFrameID == 0) {
-      // This is an empty slot -- no match
-      continue;
-    }
-    if (state.controllerIndex == aControllerIdx &&
-        state.hapticIndex == aHapticIndex) {
-      // Found pulse on same actuator -- let's override it.
-      bestSlotIndex = i;
-    }
-  }
-  ClearHapticSlot(bestSlotIndex);
-
-  // Populate the selected slot with new haptic state
-  size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
-  VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex];
-  bestSlot.inputFrameID =
-      mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID;
-  bestSlot.controllerIndex = aControllerIdx;
-  bestSlot.hapticIndex = aHapticIndex;
-  bestSlot.pulseStart = (now - mLastFrameStart[bufferIndex]).ToSeconds();
-  bestSlot.pulseDuration = aDuration;
-  bestSlot.pulseIntensity = aIntensity;
-  // Convert from seconds to ms
-  mHapticPulseRemaining[bestSlotIndex] = aDuration * 1000.0f;
-  MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length());
-  if (bestSlotIndex == mHapticPromises.Length()) {
-    mHapticPromises.AppendElement(
-        UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)));
-  } else {
-    mHapticPromises[bestSlotIndex] =
-        UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise));
-  }
-  PushState();
-}
-
-void VRDisplayExternal::ClearHapticSlot(size_t aSlot) {
-  MOZ_ASSERT(aSlot < mozilla::ArrayLength(mBrowserState.hapticState));
-  memset(&mBrowserState.hapticState[aSlot], 0, sizeof(VRHapticState));
-  mHapticPulseRemaining[aSlot] = 0.0f;
-  if (aSlot < mHapticPromises.Length() && mHapticPromises[aSlot]) {
-    VRManager* vm = VRManager::Get();
-    vm->NotifyVibrateHapticCompleted(*mHapticPromises[aSlot]);
-    mHapticPromises[aSlot] = nullptr;
-  }
-}
-
-void VRDisplayExternal::UpdateHaptics(double aDeltaTime) {
-  bool bNeedPush = false;
-  // Check for any haptic pulses that have ended and clear them
-  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
-    const VRHapticState& state = mBrowserState.hapticState[i];
-    if (state.inputFrameID == 0) {
-      // Nothing in this slot
-      continue;
-    }
-    mHapticPulseRemaining[i] -= aDeltaTime;
-    if (mHapticPulseRemaining[i] <= 0.0f) {
-      // The pulse has finished
-      ClearHapticSlot(i);
-      bNeedPush = true;
-    }
-  }
-  if (bNeedPush) {
-    PushState();
-  }
-}
-
-void VRDisplayExternal::StopVibrateHaptic(uint32_t aControllerIdx) {
-  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
-    VRHapticState& state = mBrowserState.hapticState[i];
-    if (state.controllerIndex == aControllerIdx) {
-      memset(&state, 0, sizeof(VRHapticState));
-    }
-  }
-  PushState();
-}
-
-void VRDisplayExternal::StopAllHaptics() {
-  for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) {
-    ClearHapticSlot(i);
-  }
-  PushState();
-}
-
-void VRDisplayExternal::PushState(bool aNotifyCond) {
-  VRManager* vm = VRManager::Get();
-  VRSystemManagerExternal* manager = vm->GetExternalManager();
-  manager->PushState(&mBrowserState, aNotifyCond);
-}
-
-#if defined(MOZ_WIDGET_ANDROID)
-bool VRDisplayExternal::PullState(const std::function<bool()>& aWaitCondition) {
-  VRManager* vm = VRManager::Get();
-  VRSystemManagerExternal* manager = vm->GetExternalManager();
-  return manager->PullState(&mDisplayInfo.mDisplayState, &mLastSensorState,
-                            mDisplayInfo.mControllerState, aWaitCondition);
-}
-#else
-bool VRDisplayExternal::PullState() {
-  VRManager* vm = VRManager::Get();
-  VRSystemManagerExternal* manager = vm->GetExternalManager();
-  nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
-  manager->GetHMDs(displays);
-
-  // When VR process crashes, it happenes VRDisplayHost is destroyed
-  // but its mSubmitThread is still running. We need add this
-  // to check if we still need to access its shmem.
-  if (!displays.Length()) {
-    return false;
-  }
-
-  return manager->PullState(&mDisplayInfo.mDisplayState, &mLastSensorState,
-                            mDisplayInfo.mControllerState);
-}
-#endif
-
-VRSystemManagerExternal::VRSystemManagerExternal(
-    VRExternalShmem* aAPIShmem /* = nullptr*/)
-    : mExternalShmem(aAPIShmem)
-#if !defined(MOZ_WIDGET_ANDROID)
-#  if defined(XP_WIN)
-      ,
-      mMutex(NULL)
-#  endif  // defined(XP_WIN)
-      ,
-      mSameProcess(aAPIShmem != nullptr)
-#endif  // !defined(MOZ_WIDGET_ANDROID)
-{
-#if defined(XP_MACOSX)
-  mShmemFD = 0;
-#elif defined(XP_WIN)
-  mShmemFile = NULL;
-#elif defined(MOZ_WIDGET_ANDROID)
-  mExternalStructFailed = false;
-  mEnumerationCompleted = false;
-#endif
-  mDoShutdown = false;
-
-#if defined(XP_WIN)
-  mMutex = CreateMutex(NULL,   // default security descriptor
-                       false,  // mutex not owned
-                       TEXT("mozilla::vr::ShmemMutex"));  // object name
-
-  if (mMutex == NULL) {
-    nsAutoCString msg;
-    msg.AppendPrintf("VRSystemManagerExternal CreateMutex error \"%lu\".",
-                     GetLastError());
-    NS_WARNING(msg.get());
-    MOZ_ASSERT(false);
-    return;
-  }
-  // At xpcshell extension tests, it creates multiple VRSystemManagerExternal
-  // instances in plug-contrainer.exe. It causes GetLastError() return
-  // `ERROR_ALREADY_EXISTS`. However, even though `ERROR_ALREADY_EXISTS`, it
-  // still returns the same mutex handle.
-  //
-  // https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-createmutexa
-  MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS);
-#endif  // defined(XP_WIN)
-}
-
-VRSystemManagerExternal::~VRSystemManagerExternal() {
-  CloseShmem();
-#if defined(XP_WIN)
-  if (mMutex) {
-    CloseHandle(mMutex);
-    mMutex = NULL;
-  }
-#endif
-}
-
-void VRSystemManagerExternal::OpenShmem() {
-  if (mExternalShmem) {
-    return;
-#if defined(MOZ_WIDGET_ANDROID)
-  } else if (mExternalStructFailed) {
-    return;
-#endif  // defined(MOZ_WIDGET_ANDROID)
-  }
-
-#if defined(XP_MACOSX)
-  if (mShmemFD == 0) {
-    mShmemFD =
-        shm_open(kShmemName, O_RDWR, S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH);
-  }
-  if (mShmemFD <= 0) {
-    mShmemFD = 0;
-    return;
-  }
-
-  struct stat sb;
-  fstat(mShmemFD, &sb);
-  off_t length = sb.st_size;
-  if (length < (off_t)sizeof(VRExternalShmem)) {
-    // TODO - Implement logging
-    CloseShmem();
-    return;
-  }
-
-  mExternalShmem = (VRExternalShmem*)mmap(NULL, length, PROT_READ | PROT_WRITE,
-                                          MAP_SHARED, mShmemFD, 0);
-  if (mExternalShmem == MAP_FAILED) {
-    // TODO - Implement logging
-    mExternalShmem = NULL;
-    CloseShmem();
-    return;
-  }
-
-#elif defined(XP_WIN)
-  if (mShmemFile == NULL) {
-    if (StaticPrefs::dom_vr_process_enabled()) {
-      mShmemFile =
-          CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
-                             sizeof(VRExternalShmem), kShmemName);
-      MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS);
-      MOZ_ASSERT(mShmemFile);
-    } else {
-      mShmemFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, kShmemName);
-    }
-
-    if (mShmemFile == NULL) {
-      // TODO - Implement logging
-      CloseShmem();
-      return;
-    }
-  }
-  LARGE_INTEGER length;
-  length.QuadPart = sizeof(VRExternalShmem);
-  mExternalShmem = (VRExternalShmem*)MapViewOfFile(
-      mShmemFile,           // handle to map object
-      FILE_MAP_ALL_ACCESS,  // read/write permission
-      0, 0, length.QuadPart);
-
-  if (mExternalShmem == NULL) {
-    // TODO - Implement logging
-    CloseShmem();
-    return;
-  }
-#elif defined(MOZ_WIDGET_ANDROID)
-  mExternalShmem =
-      (VRExternalShmem*)mozilla::GeckoVRManager::GetExternalContext();
-  if (!mExternalShmem) {
-    return;
-  }
-  int32_t version = -1;
-  int32_t size = 0;
-  if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
-      0) {
-    version = mExternalShmem->version;
-    size = mExternalShmem->size;
-    pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
-  } else {
-    return;
-  }
-  if (version != kVRExternalVersion) {
-    mExternalShmem = nullptr;
-    mExternalStructFailed = true;
-    return;
-  }
-  if (size != sizeof(VRExternalShmem)) {
-    mExternalShmem = nullptr;
-    mExternalStructFailed = true;
-    return;
-  }
-#endif
-  CheckForShutdown();
-}
-
-void VRSystemManagerExternal::CheckForShutdown() {
-  if (mDoShutdown) {
-    Shutdown();
-  }
-}
-
-void VRSystemManagerExternal::CloseShmem() {
-#if !defined(MOZ_WIDGET_ANDROID)
-  if (mSameProcess) {
-    return;
-  }
-#endif
-#if defined(XP_MACOSX)
-  if (mExternalShmem) {
-    munmap((void*)mExternalShmem, sizeof(VRExternalShmem));
-    mExternalShmem = NULL;
-  }
-  if (mShmemFD) {
-    close(mShmemFD);
-  }
-  mShmemFD = 0;
-#elif defined(XP_WIN)
-  if (mExternalShmem) {
-    UnmapViewOfFile((void*)mExternalShmem);
-    mExternalShmem = NULL;
-  }
-  if (mShmemFile) {
-    CloseHandle(mShmemFile);
-    mShmemFile = NULL;
-  }
-#elif defined(MOZ_WIDGET_ANDROID)
-  mExternalShmem = NULL;
-#endif
-}
-
-/*static*/
-already_AddRefed<VRSystemManagerExternal> VRSystemManagerExternal::Create(
-    VRExternalShmem* aAPIShmem /* = nullptr*/) {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (!StaticPrefs::dom_vr_enabled()) {
-    return nullptr;
-  }
-
-  if ((!StaticPrefs::dom_vr_external_enabled() && aAPIShmem == nullptr)
-#if defined(XP_WIN)
-      || !XRE_IsGPUProcess()
-#endif
-  ) {
-    return nullptr;
-  }
-
-  RefPtr<VRSystemManagerExternal> manager =
-      new VRSystemManagerExternal(aAPIShmem);
-  return manager.forget();
-}
-
-void VRSystemManagerExternal::Destroy() { Shutdown(); }
-
-void VRSystemManagerExternal::Shutdown() {
-  if (mDisplay) {
-    // We will close Shmem at the next frame to avoid
-    // mSubmitThread is still running but its shmem
-    // has been released.
-    mDisplay->ShutdownSubmitThread();
-    mDisplay = nullptr;
-  } else {
-    mDisplay = nullptr;
-    CloseShmem();
-  }
-
-  mDoShutdown = false;
-}
-
-void VRSystemManagerExternal::Run100msTasks() {
-  VRSystemManager::Run100msTasks();
-  // 1ms and 10ms tasks will always be run before
-  // the 100ms tasks, so no need to run them
-  // redundantly here.
-
-  CheckForShutdown();
-}
-
-void VRSystemManagerExternal::Enumerate() {
-  if (mDisplay == nullptr) {
-    OpenShmem();
-    if (mExternalShmem) {
-      VRDisplayState displayState;
-      memset(&displayState, 0, sizeof(VRDisplayState));
-      // We must block until enumeration has completed in order
-      // to signal that the WebVR promise should be resolved at the
-      // right time.
-#if defined(MOZ_WIDGET_ANDROID)
-      PullState(&displayState, nullptr, nullptr,
-                [&]() { return mEnumerationCompleted; });
-#else
-      while (!PullState(&displayState)) {
-#  ifdef XP_WIN
-        Sleep(0);
-#  else
-        sleep(0);
-#  endif  // XP_WIN
-      }
-#endif    // defined(MOZ_WIDGET_ANDROID)
-
-      if (displayState.isConnected) {
-        mDisplay = new VRDisplayExternal(displayState);
-      }
-    }
-  }
-}
-
-bool VRSystemManagerExternal::ShouldInhibitEnumeration() {
-  if (VRSystemManager::ShouldInhibitEnumeration()) {
-    return true;
-  }
-  if (!mEarliestRestartTime.IsNull() &&
-      mEarliestRestartTime > TimeStamp::Now()) {
-    // When the VR Service shuts down it informs us of how long we
-    // must wait until we can re-start it.
-    // We must wait until mEarliestRestartTime before attempting
-    // to enumerate again.
-    return true;
-  }
-  if (mDisplay) {
-    // When we find an a VR device, don't
-    // allow any further enumeration as it
-    // may get picked up redundantly by other
-    // API's.
-    return true;
-  }
-  return false;
-}
-
-void VRSystemManagerExternal::GetHMDs(
-    nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) {
-  if (mDisplay) {
-    aHMDResult.AppendElement(mDisplay);
-  }
-}
-
-bool VRSystemManagerExternal::GetIsPresenting() {
-  if (mDisplay) {
-    VRDisplayInfo displayInfo(mDisplay->GetDisplayInfo());
-    return displayInfo.GetPresentingGroups() != 0;
-  }
-
-  return false;
-}
-
-void VRSystemManagerExternal::VibrateHaptic(uint32_t aControllerIdx,
-                                            uint32_t aHapticIndex,
-                                            double aIntensity, double aDuration,
-                                            const VRManagerPromise& aPromise) {
-  if (mDisplay) {
-    // VRDisplayClient::FireGamepadEvents() assigns a controller ID with ranges
-    // based on displayID.  We must translate this to the indexes understood by
-    // VRDisplayExternal.
-    uint32_t controllerBaseIndex =
-        kVRControllerMaxCount * mDisplay->GetDisplayInfo().mDisplayID;
-    uint32_t controllerIndex = aControllerIdx - controllerBaseIndex;
-    double aDurationSeconds = aDuration * 0.001f;
-    mDisplay->VibrateHaptic(controllerIndex, aHapticIndex, aIntensity,
-                            aDurationSeconds, aPromise);
-  }
-}
-
-void VRSystemManagerExternal::StopVibrateHaptic(uint32_t aControllerIdx) {
-  if (mDisplay) {
-    // VRDisplayClient::FireGamepadEvents() assigns a controller ID with ranges
-    // based on displayID.  We must translate this to the indexes understood by
-    // VRDisplayExternal.
-    uint32_t controllerBaseIndex =
-        kVRControllerMaxCount * mDisplay->GetDisplayInfo().mDisplayID;
-    uint32_t controllerIndex = aControllerIdx - controllerBaseIndex;
-    mDisplay->StopVibrateHaptic(controllerIndex);
-  }
-}
-
-void VRSystemManagerExternal::GetControllers(
-    nsTArray<RefPtr<VRControllerHost>>& aControllerResult) {
-  // Controller updates are handled in VRDisplayClient for
-  // VRSystemManagerExternal
-  aControllerResult.Clear();
-}
-
-void VRSystemManagerExternal::ScanForControllers() {
-  // Controller updates are handled in VRDisplayClient for
-  // VRSystemManagerExternal
-}
-
-void VRSystemManagerExternal::HandleInput() {
-  // Controller updates are handled in VRDisplayClient for
-  // VRSystemManagerExternal
-}
-
-void VRSystemManagerExternal::RemoveControllers() {
-  if (mDisplay) {
-    mDisplay->StopAllHaptics();
-  }
-  // Controller updates are handled in VRDisplayClient for
-  // VRSystemManagerExternal
-}
-
-#if defined(MOZ_WIDGET_ANDROID)
-bool VRSystemManagerExternal::PullState(
-    VRDisplayState* aDisplayState,
-    VRHMDSensorState* aSensorState /* = nullptr */,
-    VRControllerState* aControllerState /* = nullptr */,
-    const std::function<bool()>& aWaitCondition /* = nullptr */) {
-  MOZ_ASSERT(mExternalShmem);
-  if (!mExternalShmem) {
-    return false;
-  }
-  bool done = false;
-  while (!done) {
-    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
-        0) {
-      while (true) {
-        memcpy(aDisplayState, (void*)&(mExternalShmem->state.displayState),
-               sizeof(VRDisplayState));
-        if (aSensorState) {
-          memcpy(aSensorState, (void*)&(mExternalShmem->state.sensorState),
-                 sizeof(VRHMDSensorState));
-        }
-        if (aControllerState) {
-          memcpy(aControllerState,
-                 (void*)&(mExternalShmem->state.controllerState),
-                 sizeof(VRControllerState) * kVRControllerMaxCount);
-        }
-        mEnumerationCompleted = mExternalShmem->state.enumerationCompleted;
-        if (aDisplayState->shutdown) {
-          mDoShutdown = true;
-          TimeStamp now = TimeStamp::Now();
-          if (!mEarliestRestartTime.IsNull() && mEarliestRestartTime < now) {
-            mEarliestRestartTime =
-                now + TimeDuration::FromMilliseconds(
-                          (double)aDisplayState->minRestartInterval);
-          }
-        }
-        if (!aWaitCondition || aWaitCondition()) {
-          done = true;
-          break;
-        }
-        // Block current thead using the condition variable until data changes
-        pthread_cond_wait((pthread_cond_t*)&mExternalShmem->systemCond,
-                          (pthread_mutex_t*)&mExternalShmem->systemMutex);
-      }
-      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
-    } else if (!aWaitCondition) {
-      // pthread_mutex_lock failed and we are not waiting for a condition to
-      // exit from PullState call. return false to indicate that PullState call
-      // failed
-      return false;
-    }
-  }
-  return true;
-}
-#else
-bool VRSystemManagerExternal::PullState(
-    VRDisplayState* aDisplayState,
-    VRHMDSensorState* aSensorState /* = nullptr */,
-    VRControllerState* aControllerState /* = nullptr */) {
-  bool success = false;
-  bool status = true;
-  MOZ_ASSERT(mExternalShmem);
-
-#  if defined(XP_WIN)
-  WaitForMutex lock(mMutex);
-  status = lock.GetStatus();
-#  endif  // defined(XP_WIN)
-
-  if (mExternalShmem && status) {
-    VRExternalShmem tmp;
-    memcpy(&tmp, (void*)mExternalShmem, sizeof(VRExternalShmem));
-    if (tmp.generationA == tmp.generationB && tmp.generationA != 0 &&
-        tmp.generationA != -1 && tmp.state.enumerationCompleted) {
-      memcpy(aDisplayState, &tmp.state.displayState, sizeof(VRDisplayState));
-      if (aSensorState) {
-        memcpy(aSensorState, &tmp.state.sensorState, sizeof(VRHMDSensorState));
-      }
-      if (aControllerState) {
-        memcpy(aControllerState,
-               (void*)&(mExternalShmem->state.controllerState),
-               sizeof(VRControllerState) * kVRControllerMaxCount);
-      }
-      if (aDisplayState->shutdown) {
-        mDoShutdown = true;
-        TimeStamp now = TimeStamp::Now();
-        if (!mEarliestRestartTime.IsNull() && mEarliestRestartTime < now) {
-          mEarliestRestartTime =
-              now + TimeDuration::FromMilliseconds(
-                        (double)aDisplayState->minRestartInterval);
-        }
-      }
-      success = true;
-    }
-  }
-
-  return success;
-}
-#endif    // defined(MOZ_WIDGET_ANDROID)
-
-void VRSystemManagerExternal::PushState(VRBrowserState* aBrowserState,
-                                        bool aNotifyCond) {
-  MOZ_ASSERT(aBrowserState);
-  if (mExternalShmem) {
-#if defined(MOZ_WIDGET_ANDROID)
-    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) ==
-        0) {
-      memcpy((void*)&(mExternalShmem->geckoState), aBrowserState,
-             sizeof(VRBrowserState));
-      if (aNotifyCond) {
-        pthread_cond_signal((pthread_cond_t*)&(mExternalShmem->geckoCond));
-      }
-      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex));
-    }
-#else
-    bool status = true;
-#  if defined(XP_WIN)
-    WaitForMutex lock(mMutex);
-    status = lock.GetStatus();
-#  endif  // defined(XP_WIN)
-    if (status) {
-      mExternalShmem->geckoGenerationA++;
-      memcpy((void*)&(mExternalShmem->geckoState), (void*)aBrowserState,
-             sizeof(VRBrowserState));
-      mExternalShmem->geckoGenerationB++;
-    }
-#endif    // defined(MOZ_WIDGET_ANDROID)
-  }
-}
deleted file mode 100644
--- a/gfx/vr/gfxVRExternal.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/* -*- 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 GFX_VR_EXTERNAL_H
-#define GFX_VR_EXTERNAL_H
-
-#include "nsTArray.h"
-#include "nsIScreen.h"
-#include "nsCOMPtr.h"
-#include "mozilla/RefPtr.h"
-
-#include "mozilla/gfx/2D.h"
-#include "mozilla/EnumeratedArray.h"
-#include "mozilla/UniquePtr.h"
-
-#include "gfxVR.h"
-#include "VRDisplayHost.h"
-
-#if defined(XP_MACOSX)
-class MacIOSurface;
-#endif
-namespace mozilla {
-namespace gfx {
-namespace impl {
-
-class VRDisplayExternal : public VRDisplayHost {
- public:
-  void ZeroSensor() override;
-
- protected:
-  VRHMDSensorState& GetSensorState() override;
-  void StartPresentation() override;
-  void StopPresentation() override;
-  void StartVRNavigation() override;
-  void StopVRNavigation(const TimeDuration& aTimeout) override;
-
-  bool SubmitFrame(const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
-                   const gfx::Rect& aLeftEyeRect,
-                   const gfx::Rect& aRightEyeRect) override;
-
- public:
-  explicit VRDisplayExternal(const VRDisplayState& aDisplayState);
-  void Refresh();
-  const VRControllerState& GetLastControllerState(uint32_t aStateIndex) const;
-  void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
-                     double aIntensity, double aDuration,
-                     const VRManagerPromise& aPromise);
-  void StopVibrateHaptic(uint32_t aControllerIdx);
-  void StopAllHaptics();
-  void Run1msTasks(double aDeltaTime) override;
-  void Run10msTasks() override;
-
- protected:
-  virtual ~VRDisplayExternal();
-  void Destroy();
-
- private:
-  bool PopulateLayerTexture(const layers::SurfaceDescriptor& aTexture,
-                            VRLayerTextureType* aTextureType,
-                            VRLayerTextureHandle* aTextureHandle,
-                            IntSize_POD* aTextureSize);
-  void PushState(bool aNotifyCond = false);
-#if defined(MOZ_WIDGET_ANDROID)
-  bool PullState(const std::function<bool()>& aWaitCondition = nullptr);
-#else
-  bool PullState();
-#endif
-  void ClearHapticSlot(size_t aSlot);
-  void ExpireNavigationTransition();
-  void UpdateHaptics(double aDeltaTime);
-  nsTArray<UniquePtr<VRManagerPromise>> mHapticPromises;
-  // Duration of haptic pulse time remaining (milliseconds)
-  double mHapticPulseRemaining[kVRHapticsMaxCount];
-  VRTelemetry mTelemetry;
-  TimeStamp mVRNavigationTransitionEnd;
-  VRBrowserState mBrowserState;
-  VRHMDSensorState mLastSensorState;
-};
-
-}  // namespace impl
-
-class VRSystemManagerExternal : public VRSystemManager {
- public:
-  static already_AddRefed<VRSystemManagerExternal> Create(
-      VRExternalShmem* aAPIShmem = nullptr);
-
-  void Destroy() override;
-  void Shutdown() override;
-  void Run100msTasks() override;
-  void Enumerate() override;
-  bool ShouldInhibitEnumeration() override;
-  void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
-  bool GetIsPresenting() override;
-  void HandleInput() override;
-  void GetControllers(
-      nsTArray<RefPtr<VRControllerHost>>& aControllerResult) override;
-  void ScanForControllers() override;
-  void RemoveControllers() override;
-  void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
-                     double aIntensity, double aDuration,
-                     const VRManagerPromise& aPromise) override;
-  void StopVibrateHaptic(uint32_t aControllerIdx) override;
-#if defined(MOZ_WIDGET_ANDROID)
-  bool PullState(VRDisplayState* aDisplayState,
-                 VRHMDSensorState* aSensorState = nullptr,
-                 VRControllerState* aControllerState = nullptr,
-                 const std::function<bool()>& aWaitCondition = nullptr);
-#else
-  bool PullState(VRDisplayState* aDisplayState,
-                 VRHMDSensorState* aSensorState = nullptr,
-                 VRControllerState* aControllerState = nullptr);
-#endif
-  void PushState(VRBrowserState* aBrowserState, const bool aNotifyCond = false);
-
- protected:
-  explicit VRSystemManagerExternal(VRExternalShmem* aAPIShmem = nullptr);
-  virtual ~VRSystemManagerExternal();
-
- private:
-  // there can only be one
-  RefPtr<impl::VRDisplayExternal> mDisplay;
-#if defined(XP_MACOSX)
-  int mShmemFD;
-#elif defined(XP_WIN)
-  base::ProcessHandle mShmemFile;
-#elif defined(MOZ_WIDGET_ANDROID)
-
-  bool mExternalStructFailed;
-  bool mEnumerationCompleted;
-#endif
-  bool mDoShutdown;
-
-  volatile VRExternalShmem* mExternalShmem;
-
-#if defined(XP_WIN)
-  HANDLE mMutex;
-#endif
-#if !defined(MOZ_WIDGET_ANDROID)
-  bool mSameProcess;
-#endif
-  TimeStamp mEarliestRestartTime;
-
-  void OpenShmem();
-  void CloseShmem();
-  void CheckForShutdown();
-};
-
-}  // namespace gfx
-}  // namespace mozilla
-
-#endif /* GFX_VR_EXTERNAL_H */
deleted file mode 100644
--- a/gfx/vr/gfxVRPuppet.cpp
+++ /dev/null
@@ -1,880 +0,0 @@
-/* -*- 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/. */
-
-#if defined(XP_WIN)
-#  include "CompositorD3D11.h"
-#  include "TextureD3D11.h"
-#  include "mozilla/gfx/DeviceManagerDx.h"
-#elif defined(XP_MACOSX)
-#  include "mozilla/gfx/MacIOSurface.h"
-#endif
-
-#include "mozilla/Base64.h"
-#include "mozilla/StaticPrefs.h"
-#include "mozilla/gfx/DataSurfaceHelpers.h"
-#include "mozilla/layers/CompositorThread.h"  // for CompositorThreadHolder
-#include "gfxUtils.h"
-#include "gfxVRPuppet.h"
-#include "VRManager.h"
-#include "VRThread.h"
-
-#include "mozilla/dom/GamepadEventTypes.h"
-#include "mozilla/dom/GamepadBinding.h"
-
-// See CompositorD3D11Shaders.h
-namespace mozilla {
-namespace layers {
-struct ShaderBytes {
-  const void* mData;
-  size_t mLength;
-};
-extern ShaderBytes sRGBShader;
-extern ShaderBytes sLayerQuadVS;
-}  // namespace layers
-}  // namespace mozilla
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::gfx::impl;
-using namespace mozilla::layers;
-
-// Reminder: changing the order of these buttons may break web content
-static const uint64_t kPuppetButtonMask[] = {1, 2, 4, 8};
-static const uint32_t kNumPuppetButtonMask =
-    sizeof(kPuppetButtonMask) / sizeof(uint64_t);
-static const uint32_t kNumPuppetAxis = 3;
-static const uint32_t kNumPuppetHaptcs = 1;
-
-VRDisplayPuppet::VRDisplayPuppet()
-    : VRDisplayLocal(VRDeviceType::Puppet),
-      mIsPresenting(false),
-      mSensorState{} {
-  MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayLocal);
-
-  VRDisplayState& state = mDisplayInfo.mDisplayState;
-  strncpy(state.displayName, "Puppet HMD", kVRDisplayNameMaxLen);
-  state.isConnected = true;
-  state.isMounted = false;
-  state.capabilityFlags = VRDisplayCapabilityFlags::Cap_None |
-                          VRDisplayCapabilityFlags::Cap_Orientation |
-                          VRDisplayCapabilityFlags::Cap_Position |
-                          VRDisplayCapabilityFlags::Cap_External |
-                          VRDisplayCapabilityFlags::Cap_Present |
-                          VRDisplayCapabilityFlags::Cap_StageParameters;
-  state.eyeResolution.width = 1836;   // 1080 * 1.7
-  state.eyeResolution.height = 2040;  // 1200 * 1.7
-
-  // SteamVR gives the application a single FOV to use; it's not configurable as
-  // with Oculus
-  for (uint32_t eye = 0; eye < 2; ++eye) {
-    state.eyeTranslation[eye].x = 0.0f;
-    state.eyeTranslation[eye].y = 0.0f;
-    state.eyeTranslation[eye].z = 0.0f;
-    state.eyeFOV[eye] = VRFieldOfView(45.0, 45.0, 45.0, 45.0);
-  }
-
-  // default: 1m x 1m space, 0.75m high in seated position
-  state.stageSize.width = 1.0f;
-  state.stageSize.height = 1.0f;
-
-  state.sittingToStandingTransform[0] = 1.0f;
-  state.sittingToStandingTransform[1] = 0.0f;
-  state.sittingToStandingTransform[2] = 0.0f;
-  state.sittingToStandingTransform[3] = 0.0f;
-
-  state.sittingToStandingTransform[4] = 0.0f;
-  state.sittingToStandingTransform[5] = 1.0f;
-  state.sittingToStandingTransform[6] = 0.0f;
-  state.sittingToStandingTransform[7] = 0.0f;
-
-  state.sittingToStandingTransform[8] = 0.0f;
-  state.sittingToStandingTransform[9] = 0.0f;
-  state.sittingToStandingTransform[10] = 1.0f;
-  state.sittingToStandingTransform[11] = 0.0f;
-
-  state.sittingToStandingTransform[12] = 0.0f;
-  state.sittingToStandingTransform[13] = 0.75f;
-  state.sittingToStandingTransform[14] = 0.0f;
-  state.sittingToStandingTransform[15] = 1.0f;
-
-  gfx::Quaternion rot;
-
-  mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
-  mSensorState.pose.orientation[0] = rot.x;
-  mSensorState.pose.orientation[1] = rot.y;
-  mSensorState.pose.orientation[2] = rot.z;
-  mSensorState.pose.orientation[3] = rot.w;
-  mSensorState.pose.angularVelocity[0] = 0.0f;
-  mSensorState.pose.angularVelocity[1] = 0.0f;
-  mSensorState.pose.angularVelocity[2] = 0.0f;
-
-  mSensorState.flags |= VRDisplayCapabilityFlags::Cap_Position;
-  mSensorState.pose.position[0] = 0.0f;
-  mSensorState.pose.position[1] = 0.0f;
-  mSensorState.pose.position[2] = 0.0f;
-  mSensorState.pose.linearVelocity[0] = 0.0f;
-  mSensorState.pose.linearVelocity[1] = 0.0f;
-  mSensorState.pose.linearVelocity[2] = 0.0f;
-}
-
-VRDisplayPuppet::~VRDisplayPuppet() {
-  MOZ_COUNT_DTOR_INHERITED(VRDisplayPuppet, VRDisplayLocal);
-}
-
-void VRDisplayPuppet::SetDisplayInfo(const VRDisplayInfo& aDisplayInfo) {
-  // We are only interested in the eye and mount info of the display info.
-  VRDisplayState& state = mDisplayInfo.mDisplayState;
-  state.eyeResolution = aDisplayInfo.mDisplayState.eyeResolution;
-  state.isMounted = aDisplayInfo.mDisplayState.isMounted;
-  memcpy(&state.eyeFOV, &aDisplayInfo.mDisplayState.eyeFOV,
-         sizeof(state.eyeFOV[0]) * VRDisplayState::NumEyes);
-  memcpy(&state.eyeTranslation, &aDisplayInfo.mDisplayState.eyeTranslation,
-         sizeof(state.eyeTranslation[0]) * VRDisplayState::NumEyes);
-}
-
-void VRDisplayPuppet::Destroy() { StopPresentation(); }
-
-void VRDisplayPuppet::ZeroSensor() {}
-
-VRHMDSensorState& VRDisplayPuppet::GetSensorState() {
-  mSensorState.inputFrameID = mDisplayInfo.mFrameId;
-
-  Matrix4x4 matHeadToEye[2];
-  for (uint32_t eye = 0; eye < 2; ++eye) {
-    matHeadToEye[eye].PreTranslate(mDisplayInfo.GetEyeTranslation(eye));
-  }
-  mSensorState.CalcViewMatrices(matHeadToEye);
-
-  return mSensorState;
-}
-
-void VRDisplayPuppet::SetSensorState(const VRHMDSensorState& aSensorState) {
-  memcpy(&mSensorState, &aSensorState, sizeof(mSensorState));
-}
-
-void VRDisplayPuppet::StartPresentation() {
-  if (mIsPresenting) {
-    return;
-  }
-  mIsPresenting = true;
-
-#if defined(XP_WIN)
-  if (!CreateD3DObjects()) {
-    return;
-  }
-
-  if (FAILED(mDevice->CreateVertexShader(
-          sLayerQuadVS.mData, sLayerQuadVS.mLength, nullptr, &mQuadVS))) {
-    NS_WARNING("Failed to create vertex shader for Puppet");
-    return;
-  }
-
-  if (FAILED(mDevice->CreatePixelShader(sRGBShader.mData, sRGBShader.mLength,
-                                        nullptr, &mQuadPS))) {
-    NS_WARNING("Failed to create pixel shader for Puppet");
-    return;
-  }
-
-  CD3D11_BUFFER_DESC cBufferDesc(sizeof(layers::VertexShaderConstants),
-                                 D3D11_BIND_CONSTANT_BUFFER,
-                                 D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
-
-  if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr,
-                                   getter_AddRefs(mVSConstantBuffer)))) {
-    NS_WARNING("Failed to vertex shader constant buffer for Puppet");
-    return;
-  }
-
-  cBufferDesc.ByteWidth = sizeof(layers::PixelShaderConstants);
-  if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr,
-                                   getter_AddRefs(mPSConstantBuffer)))) {
-    NS_WARNING("Failed to pixel shader constant buffer for Puppet");
-    return;
-  }
-
-  CD3D11_SAMPLER_DESC samplerDesc(D3D11_DEFAULT);
-  if (FAILED(mDevice->CreateSamplerState(
-          &samplerDesc, getter_AddRefs(mLinearSamplerState)))) {
-    NS_WARNING("Failed to create sampler state for Puppet");
-    return;
-  }
-
-  D3D11_INPUT_ELEMENT_DESC layout[] = {
-      {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0,
-       D3D11_INPUT_PER_VERTEX_DATA, 0},
-  };
-
-  if (FAILED(mDevice->CreateInputLayout(
-          layout, sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC),
-          sLayerQuadVS.mData, sLayerQuadVS.mLength,
-          getter_AddRefs(mInputLayout)))) {
-    NS_WARNING("Failed to create input layout for Puppet");
-    return;
-  }
-
-  Vertex vertices[] = {{{0.0, 0.0}}, {{1.0, 0.0}}, {{0.0, 1.0}}, {{1.0, 1.0}}};
-  CD3D11_BUFFER_DESC bufferDesc(sizeof(vertices), D3D11_BIND_VERTEX_BUFFER);
-  D3D11_SUBRESOURCE_DATA data;
-  data.pSysMem = (void*)vertices;
-
-  if (FAILED(mDevice->CreateBuffer(&bufferDesc, &data,
-                                   getter_AddRefs(mVertexBuffer)))) {
-    NS_WARNING("Failed to create vertex buffer for Puppet");
-    return;
-  }
-
-  memset(&mVSConstants, 0, sizeof(mVSConstants));
-  memset(&mPSConstants, 0, sizeof(mPSConstants));
-#endif  // XP_WIN
-}
-
-void VRDisplayPuppet::StopPresentation() {
-  if (!mIsPresenting) {
-    return;
-  }
-
-  mIsPresenting = false;
-}
-
-#if defined(XP_WIN)
-bool VRDisplayPuppet::UpdateConstantBuffers() {
-  HRESULT hr;
-  D3D11_MAPPED_SUBRESOURCE resource;
-  resource.pData = nullptr;
-
-  hr = mContext->Map(mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0,
-                     &resource);
-  if (FAILED(hr) || !resource.pData) {
-    return false;
-  }
-  *(VertexShaderConstants*)resource.pData = mVSConstants;
-  mContext->Unmap(mVSConstantBuffer, 0);
-  resource.pData = nullptr;
-
-  hr = mContext->Map(mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0,
-                     &resource);
-  if (FAILED(hr) || !resource.pData) {
-    return false;
-  }
-  *(PixelShaderConstants*)resource.pData = mPSConstants;
-  mContext->Unmap(mPSConstantBuffer, 0);
-
-  ID3D11Buffer* buffer = mVSConstantBuffer;
-  mContext->VSSetConstantBuffers(0, 1, &buffer);
-  buffer = mPSConstantBuffer;
-  mContext->PSSetConstantBuffers(0, 1, &buffer);
-  return true;
-}
-
-bool VRDisplayPuppet::SubmitFrame(ID3D11Texture2D* aSource,
-                                  const IntSize& aSize,
-                                  const gfx::Rect& aLeftEyeRect,
-                                  const gfx::Rect& aRightEyeRect) {
-  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
-  if (!mIsPresenting) {
-    return false;
-  }
-
-  if (!CreateD3DObjects()) {
-    return false;
-  }
-  AutoRestoreRenderState restoreState(this);
-  if (!restoreState.IsSuccess()) {
-    return false;
-  }
-
-  VRManager* vm = VRManager::Get();
-  MOZ_ASSERT(vm);
-
-  switch (StaticPrefs::dom_vr_puppet_submitframe()) {
-    case 0:
-      // The VR frame is not displayed.
-      break;
-    case 1: {
-      // The frames are submitted to VR compositor are decoded
-      // into a base64Image and dispatched to the DOM side.
-      D3D11_TEXTURE2D_DESC desc;
-      aSource->GetDesc(&desc);
-      MOZ_ASSERT(desc.Format == DXGI_FORMAT_B8G8R8A8_UNORM,
-                 "Only support B8G8R8A8_UNORM format.");
-      // Map the staging resource
-      ID3D11Texture2D* mappedTexture = nullptr;
-      D3D11_MAPPED_SUBRESOURCE mapInfo;
-      HRESULT hr = mContext->Map(aSource,
-                                 0,  // Subsource
-                                 D3D11_MAP_READ,
-                                 0,  // MapFlags
-                                 &mapInfo);
-
-      if (FAILED(hr)) {
-        // If we can't map this texture, copy it to a staging resource.
-        if (hr == E_INVALIDARG) {
-          D3D11_TEXTURE2D_DESC desc2;
-          desc2.Width = desc.Width;
-          desc2.Height = desc.Height;
-          desc2.MipLevels = desc.MipLevels;
-          desc2.ArraySize = desc.ArraySize;
-          desc2.Format = desc.Format;
-          desc2.SampleDesc = desc.SampleDesc;
-          desc2.Usage = D3D11_USAGE_STAGING;
-          desc2.BindFlags = 0;
-          desc2.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
-          desc2.MiscFlags = 0;
-
-          ID3D11Texture2D* stagingTexture = nullptr;
-          hr = mDevice->CreateTexture2D(&desc2, nullptr, &stagingTexture);
-          if (FAILED(hr)) {
-            MOZ_ASSERT(false, "Failed to create a staging texture");
-            return false;
-          }
-          // Copy the texture to a staging resource
-          mContext->CopyResource(stagingTexture, aSource);
-          // Map the staging resource
-          hr = mContext->Map(stagingTexture,
-                             0,  // Subsource
-                             D3D11_MAP_READ,
-                             0,  // MapFlags
-                             &mapInfo);
-          if (FAILED(hr)) {
-            MOZ_ASSERT(false, "Failed to map staging texture");
-          }
-          mappedTexture = stagingTexture;
-        } else {
-          MOZ_ASSERT(false, "Failed to map staging texture");
-          return false;
-        }
-      } else {
-        mappedTexture = aSource;
-      }
-      // Ideally, we should convert the srcData to a PNG image and decode it
-      // to a Base64 string here, but the GPU process does not have the
-      // privilege to access the image library. So, we have to convert the RAW
-      // image data to a base64 string and forward it to let the content process
-      // to do the image conversion.
-      const char* srcData = static_cast<const char*>(mapInfo.pData);
-      VRSubmitFrameResultInfo result;
-      result.mFormat = SurfaceFormat::B8G8R8A8;
-      result.mWidth = desc.Width;
-      result.mHeight = desc.Height;
-      result.mFrameNum = mDisplayInfo.mFrameId;
-      // If the original texture size is not pow of 2, the data will not be
-      // tightly strided. We have to copy the pixels by rows.
-      nsCString rawString;
-      for (uint32_t i = 0; i < desc.Height; i++) {
-        rawString += Substring(srcData + i * mapInfo.RowPitch, desc.Width * 4);
-      }
-      mContext->Unmap(mappedTexture, 0);
-
-      if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
-        MOZ_ASSERT(false, "Failed to encode base64 images.");
-      }
-      // Dispatch the base64 encoded string to the DOM side. Then, it will be
-      // decoded and convert to a PNG image there.
-      MessageLoop* loop = CompositorThreadHolder::Loop();
-      loop->PostTask(NewRunnableMethod<const uint32_t, VRSubmitFrameResultInfo>(
-          "VRManager::DispatchSubmitFrameResult", vm,
-          &VRManager::DispatchSubmitFrameResult, mDisplayInfo.mDisplayID,
-          result));
-      break;
-    }
-    case 2: {
-      // The VR compositor sumbmit frame to the screen window,
-      // the current coordinate is at (0, 0, width, height).
-      Matrix viewMatrix = Matrix::Translation(-1.0, 1.0);
-      viewMatrix.PreScale(2.0f / float(aSize.width),
-                          2.0f / float(aSize.height));
-      viewMatrix.PreScale(1.0f, -1.0f);
-      Matrix4x4 projection = Matrix4x4::From2D(viewMatrix);
-      projection._33 = 0.0f;
-
-      Matrix transform2d;
-      gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d);
-
-      const float posX = 0.0f, posY = 0.0f;
-      D3D11_VIEWPORT viewport;
-      viewport.MinDepth = 0.0f;
-      viewport.MaxDepth = 1.0f;
-      viewport.Width = aSize.width;
-      viewport.Height = aSize.height;
-      viewport.TopLeftX = posX;
-      viewport.TopLeftY = posY;
-
-      D3D11_RECT scissor;
-      scissor.left = posX;
-      scissor.right = aSize.width + posX;
-      scissor.top = posY;
-      scissor.bottom = aSize.height + posY;
-
-      memcpy(&mVSConstants.layerTransform, &transform._11,
-             sizeof(mVSConstants.layerTransform));
-      memcpy(&mVSConstants.projection, &projection._11,
-             sizeof(mVSConstants.projection));
-      mVSConstants.renderTargetOffset[0] = 0.0f;
-      mVSConstants.renderTargetOffset[1] = 0.0f;
-      mVSConstants.layerQuad = Rect(0.0f, 0.0f, aSize.width, aSize.height);
-      mVSConstants.textureCoords = Rect(0.0f, 1.0f, 1.0f, -1.0f);
-
-      mPSConstants.layerOpacity[0] = 1.0f;
-
-      ID3D11Buffer* vbuffer = mVertexBuffer;
-      UINT vsize = sizeof(Vertex);
-      UINT voffset = 0;
-      mContext->IASetVertexBuffers(0, 1, &vbuffer, &vsize, &voffset);
-      mContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
-      mContext->IASetInputLayout(mInputLayout);
-      mContext->RSSetViewports(1, &viewport);
-      mContext->RSSetScissorRects(1, &scissor);
-      mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
-      mContext->VSSetShader(mQuadVS, nullptr, 0);
-      mContext->PSSetShader(mQuadPS, nullptr, 0);
-
-      RefPtr<ID3D11ShaderResourceView> srView;
-      HRESULT hr = mDevice->CreateShaderResourceView(aSource, nullptr,
-                                                     getter_AddRefs(srView));
-      if (FAILED(hr) || !srView) {
-        gfxWarning() << "Could not create shader resource view for Puppet: "
-                     << hexa(hr);
-        return false;
-      }
-      ID3D11ShaderResourceView* viewPtr = srView.get();
-      mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &viewPtr);
-      // XXX Use Constant from TexSlot in CompositorD3D11.cpp?
-
-      ID3D11SamplerState* sampler = mLinearSamplerState;
-      mContext->PSSetSamplers(0, 1, &sampler);
-
-      if (!UpdateConstantBuffers()) {
-        NS_WARNING("Failed to update constant buffers for Puppet");
-        return false;
-      }
-      mContext->Draw(4, 0);
-      break;
-    }
-  }
-
-  // We will always return false for gfxVRPuppet to ensure that the fallback
-  // "watchdog" code in VRDisplayHost::NotifyVSync() throttles the render loop.
-  // This "watchdog" will result in a refresh rate that is quite low compared to
-  // real hardware, but should be sufficient for non-performance oriented tests.
-  // If we wish to simulate realistic frame rates with VRDisplayPuppet, we
-  // should block here for the appropriate amount of time and return true to
-  // indicate that we have blocked.
-  return false;
-}
-
-#elif defined(XP_MACOSX)
-
-bool VRDisplayPuppet::SubmitFrame(MacIOSurface* aMacIOSurface,
-                                  const IntSize& aSize,
-                                  const gfx::Rect& aLeftEyeRect,
-                                  const gfx::Rect& aRightEyeRect) {
-  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
-  if (!mIsPresenting || !aMacIOSurface) {
-    return false;
-  }
-
-  VRManager* vm = VRManager::Get();
-  MOZ_ASSERT(vm);
-
-  switch (StaticPrefs::dom_vr_puppet_submitframe()) {
-    case 0:
-      // The VR frame is not displayed.
-      break;
-    case 1: {
-      // The frames are submitted to VR compositor are decoded
-      // into a base64Image and dispatched to the DOM side.
-      RefPtr<SourceSurface> surf = aMacIOSurface->GetAsSurface();
-      RefPtr<DataSourceSurface> dataSurf =
-          surf ? surf->GetDataSurface() : nullptr;
-      if (dataSurf) {
-        // Ideally, we should convert the srcData to a PNG image and decode it
-        // to a Base64 string here, but the GPU process does not have the
-        // privilege to access the image library. So, we have to convert the RAW
-        // image data to a base64 string and forward it to let the content
-        // process to do the image conversion.
-        DataSourceSurface::MappedSurface map;
-        if (!dataSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
-          MOZ_ASSERT(false, "Read DataSourceSurface fail.");
-          return false;
-        }
-        const uint8_t* srcData = map.mData;
-        const auto& surfSize = dataSurf->GetSize();
-        VRSubmitFrameResultInfo result;
-        result.mFormat = SurfaceFormat::B8G8R8A8;
-        result.mWidth = surfSize.width;
-        result.mHeight = surfSize.height;
-        result.mFrameNum = mDisplayInfo.mFrameId;
-        // If the original texture size is not pow of 2, the data will not be
-        // tightly strided. We have to copy the pixels by rows.
-        nsCString rawString;
-        for (int32_t i = 0; i < surfSize.height; i++) {
-          rawString += Substring((const char*)(srcData) + i * map.mStride,
-                                 surfSize.width * 4);
-        }
-        dataSurf->Unmap();
-
-        if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
-          MOZ_ASSERT(false, "Failed to encode base64 images.");
-        }
-        // Dispatch the base64 encoded string to the DOM side. Then, it will be
-        // decoded and convert to a PNG image there.
-        MessageLoop* loop = CompositorThreadHolder::Loop();
-        loop->PostTask(
-            NewRunnableMethod<const uint32_t, VRSubmitFrameResultInfo>(
-                "VRManager::DispatchSubmitFrameResult", vm,
-                &VRManager::DispatchSubmitFrameResult, mDisplayInfo.mDisplayID,
-                result));
-      }
-      break;
-    }
-    case 2: {
-      MOZ_ASSERT(false, "No support for showing VR frames on MacOSX yet.");
-      break;
-    }
-  }
-
-  return false;
-}
-
-#elif defined(MOZ_WIDGET_ANDROID)
-
-bool VRDisplayPuppet::SubmitFrame(
-    const mozilla::layers::SurfaceTextureDescriptor& aDescriptor,
-    const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) {
-  MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
-  return false;
-}
-
-#endif
-
-void VRDisplayPuppet::Refresh() {
-  // We update mIsConneced once per refresh.
-  mDisplayInfo.mDisplayState.isConnected = true;
-}
-
-VRControllerPuppet::VRControllerPuppet(dom::GamepadHand aHand,
-                                       uint32_t aDisplayID)
-    : VRControllerHost(VRDeviceType::Puppet, aHand, aDisplayID),
-      mButtonPressState(0),
-      mButtonTouchState(0) {
-  MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
-  VRControllerState& state = mControllerInfo.mControllerState;
-  strncpy(state.controllerName, "Puppet Gamepad", kVRControllerNameMaxLen);
-  state.numButtons = kNumPuppetButtonMask;
-  state.numAxes = kNumPuppetAxis;
-  state.numHaptics = kNumPuppetHaptcs;
-}
-
-VRControllerPuppet::~VRControllerPuppet() {
-  MOZ_COUNT_DTOR_INHERITED(VRControllerPuppet, VRControllerHost);
-}
-
-void VRControllerPuppet::SetButtonPressState(uint32_t aButton, bool aPressed) {
-  const uint64_t buttonMask = kPuppetButtonMask[aButton];
-  uint64_t pressedBit = GetButtonPressed();
-
-  if (aPressed) {
-    pressedBit |= kPuppetButtonMask[aButton];
-  } else if (pressedBit & buttonMask) {
-    // this button was pressed but is released now.
-    uint64_t mask = 0xff ^ buttonMask;
-    pressedBit &= mask;
-  }
-
-  mButtonPressState = pressedBit;
-}
-
-uint64_t VRControllerPuppet::GetButtonPressState() { return mButtonPressState; }
-
-void VRControllerPuppet::SetButtonTouchState(uint32_t aButton, bool aTouched) {
-  const uint64_t buttonMask = kPuppetButtonMask[aButton];
-  uint64_t touchedBit = GetButtonTouched();
-
-  if (aTouched) {
-    touchedBit |= kPuppetButtonMask[aButton];
-  } else if (touchedBit & buttonMask) {
-    // this button was touched but is released now.
-    uint64_t mask = 0xff ^ buttonMask;
-    touchedBit &= mask;
-  }
-
-  mButtonTouchState = touchedBit;
-}
-
-uint64_t VRControllerPuppet::GetButtonTouchState() { return mButtonTouchState; }
-
-void VRControllerPuppet::SetAxisMoveState(uint32_t aAxis, double aValue) {
-  MOZ_ASSERT((sizeof(mAxisMoveState) / sizeof(float)) == kNumPuppetAxis);
-  MOZ_ASSERT(aAxis <= kNumPuppetAxis);
-
-  mAxisMoveState[aAxis] = aValue;
-}
-
-double VRControllerPuppet::GetAxisMoveState(uint32_t aAxis) {
-  return mAxisMoveState[aAxis];
-}
-
-void VRControllerPuppet::SetPoseMoveState(const dom::GamepadPoseState& aPose) {
-  mPoseState = aPose;
-}
-
-const dom::GamepadPoseState& VRControllerPuppet::GetPoseMoveState() {
-  return mPoseState;
-}
-
-float VRControllerPuppet::GetAxisMove(uint32_t aAxis) {
-  return mControllerInfo.mControllerState.axisValue[aAxis];
-}
-
-void VRControllerPuppet::SetAxisMove(uint32_t aAxis, float aValue) {
-  mControllerInfo.mControllerState.axisValue[aAxis] = aValue;
-}
-
-VRSystemManagerPuppet::VRSystemManagerPuppet()
-    : mPuppetDisplayCount(0),
-      mPuppetDisplayInfo{},
-      mPuppetDisplaySensorState{} {}
-
-/*static*/
-already_AddRefed<VRSystemManagerPuppet> VRSystemManagerPuppet::Create() {
-  if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_puppet_enabled()) {
-    return nullptr;
-  }
-
-  RefPtr<VRSystemManagerPuppet> manager = new VRSystemManagerPuppet();
-  return manager.forget();
-}
-
-void VRSystemManagerPuppet::Destroy() { Shutdown(); }
-
-void VRSystemManagerPuppet::Shutdown() { mPuppetHMDs.Clear(); }
-
-void VRSystemManagerPuppet::Run10msTasks() {
-  VRSystemManager::Run10msTasks();
-
-  /**
-   * When running headless mochitests on some of our automated test
-   * infrastructure, 2d display vsyncs are not always generated.
-   * To workaround, we produce a vsync manually.
-   */
-  VRManager* vm = VRManager::Get();
-  MOZ_ASSERT(vm);
-  vm->NotifyVsync(TimeStamp::Now());
-}
-
-void VRSystemManagerPuppet::NotifyVSync() {
-  VRSystemManager::NotifyVSync();
-
-  for (const auto& display : mPuppetHMDs) {
-    display->Refresh();
-  }
-}
-
-uint32_t VRSystemManagerPuppet::CreateTestDisplay() {
-  if (mPuppetDisplayCount >= kMaxPuppetDisplays) {
-    MOZ_ASSERT(false);
-    return mPuppetDisplayCount;
-  }
-  return mPuppetDisplayCount++;
-}
-
-void VRSystemManagerPuppet::ClearTestDisplays() { mPuppetDisplayCount = 0; }
-
-void VRSystemManagerPuppet::Enumerate() {
-  while (mPuppetHMDs.Length() < mPuppetDisplayCount) {
-    VRDisplayPuppet* puppetDisplay = new VRDisplayPuppet();
-    uint32_t deviceID = mPuppetHMDs.Length();
-    puppetDisplay->SetDisplayInfo(mPuppetDisplayInfo[deviceID]);
-    puppetDisplay->SetSensorState(mPuppetDisplaySensorState[deviceID]);
-    mPuppetHMDs.AppendElement(puppetDisplay);
-  }
-  while (mPuppetHMDs.Length() > mPuppetDisplayCount) {
-    mPuppetHMDs.RemoveLastElement();
-  }
-}
-
-void VRSystemManagerPuppet::SetPuppetDisplayInfo(
-    const uint32_t& aDeviceID, const VRDisplayInfo& aDisplayInfo) {
-  if (aDeviceID >= mPuppetDisplayCount) {
-    MOZ_ASSERT(false);
-    return;
-  }
-  /**
-   * Even if mPuppetHMDs.Length() <= aDeviceID, we need to
-   * update mPuppetDisplayInfo[aDeviceID].  In the case that
-   * a puppet display is added and SetPuppetDisplayInfo is
-   * immediately called, mPuppetHMDs may not be populated yet.
-   * VRSystemManagerPuppet::Enumerate() will initialize
-   * the VRDisplayPuppet later using mPuppetDisplayInfo.
-   */
-  mPuppetDisplayInfo[aDeviceID] = aDisplayInfo;
-  if (mPuppetHMDs.Length() > aDeviceID) {
-    /**
-     * In the event that the VRDisplayPuppet has already been
-     * created, we update it directly.
-     */
-    mPuppetHMDs[aDeviceID]->SetDisplayInfo(aDisplayInfo);
-  }
-}
-
-void VRSystemManagerPuppet::SetPuppetDisplaySensorState(
-    const uint32_t& aDeviceID, const VRHMDSensorState& aSensorState) {
-  if (aDeviceID >= mPuppetDisplayCount) {
-    MOZ_ASSERT(false);
-    return;
-  }
-  /**
-   * Even if mPuppetHMDs.Length() <= aDeviceID, we need to
-   * update mPuppetDisplaySensorState[aDeviceID].  In the case that
-   * a puppet display is added and SetPuppetDisplaySensorState is
-   * immediately called, mPuppetHMDs may not be populated yet.
-   * VRSystemManagerPuppet::Enumerate() will initialize
-   * the VRDisplayPuppet later using mPuppetDisplaySensorState.
-   */
-  mPuppetDisplaySensorState[aDeviceID] = aSensorState;
-  if (mPuppetHMDs.Length() > aDeviceID) {
-    /**
-     * In the event that the VRDisplayPuppet has already been
-     * created, we update it directly.
-     */
-    mPuppetHMDs[aDeviceID]->SetSensorState(aSensorState);
-  }
-}
-
-void VRSystemManagerPuppet::GetHMDs(
-    nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) {
-  for (auto display : mPuppetHMDs) {
-    aHMDResult.AppendElement(display);
-  }
-}
-
-bool VRSystemManagerPuppet::GetIsPresenting() {
-  for (const auto& display : mPuppetHMDs) {
-    const VRDisplayInfo& displayInfo(display->GetDisplayInfo());
-    if (displayInfo.GetPresentingGroups() != kVRGroupNone) {
-      return true;
-    }
-  }
-  return false;
-}
-
-void VRSystemManagerPuppet::HandleInput() {
-  RefPtr<impl::VRControllerPuppet> controller;
-  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
-    controller = mPuppetController[i];
-    for (uint32_t j = 0; j < kNumPuppetButtonMask; ++j) {
-      HandleButtonPress(i, j, kPuppetButtonMask[j],
-                        controller->GetButtonPressState(),
-                        controller->GetButtonTouchState());
-    }
-    controller->SetButtonPressed(controller->GetButtonPressState());
-    controller->SetButtonTouched(controller->GetButtonTouchState());
-
-    for (uint32_t j = 0; j < kNumPuppetAxis; ++j) {
-      HandleAxisMove(i, j, controller->GetAxisMoveState(j));
-    }
-    HandlePoseTracking(i, controller->GetPoseMoveState(), controller);
-  }
-}
-
-void VRSystemManagerPuppet::HandleButtonPress(uint32_t aControllerIdx,
-                                              uint32_t aButton,
-                                              uint64_t aButtonMask,
-                                              uint64_t aButtonPressed,
-                                              uint64_t aButtonTouched) {
-  RefPtr<impl::VRControllerPuppet> controller(
-      mPuppetController[aControllerIdx]);
-  MOZ_ASSERT(controller);
-  const uint64_t pressedDiff =
-      (controller->GetButtonPressed() ^ aButtonPressed);
-  const uint64_t touchedDiff =
-      (controller->GetButtonTouched() ^ aButtonTouched);
-
-  if (!pressedDiff && !touchedDiff) {
-    return;
-  }
-
-  if (pressedDiff & aButtonMask || touchedDiff & aButtonMask) {
-    // diff & (aButtonPressed, aButtonTouched) would be true while a new button
-    // pressed or touched event, otherwise it is an old event and needs to
-    // notify the button has been released.
-    NewButtonEvent(aControllerIdx, aButton, aButtonMask & aButtonPressed,
-                   aButtonMask & aButtonPressed,
-                   (aButtonMask & aButtonPressed) ? 1.0L : 0.0L);
-  }
-}
-
-void VRSystemManagerPuppet::HandleAxisMove(uint32_t aControllerIdx,
-                                           uint32_t aAxis, float aValue) {
-  RefPtr<impl::VRControllerPuppet> controller(
-      mPuppetController[aControllerIdx]);
-  MOZ_ASSERT(controller);
-
-  if (controller->GetAxisMove(aAxis) != aValue) {
-    NewAxisMove(aControllerIdx, aAxis, aValue);
-    controller->SetAxisMove(aAxis, aValue);
-  }
-}
-
-void VRSystemManagerPuppet::HandlePoseTracking(
-    uint32_t aControllerIdx, const dom::GamepadPoseState& aPose,
-    VRControllerHost* aController) {
-  MOZ_ASSERT(aController);
-  if (aPose != aController->GetPose()) {
-    aController->SetPose(aPose);
-    NewPoseState(aControllerIdx, aPose);
-  }
-}
-
-void VRSystemManagerPuppet::VibrateHaptic(uint32_t aControllerIdx,
-                                          uint32_t aHapticIndex,
-                                          double aIntensity, double aDuration,
-                                          const VRManagerPromise& aPromise) {}
-
-void VRSystemManagerPuppet::StopVibrateHaptic(uint32_t aControllerIdx) {}
-
-void VRSystemManagerPuppet::GetControllers(
-    nsTArray<RefPtr<VRControllerHost>>& aControllerResult) {
-  aControllerResult.Clear();
-  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
-    aControllerResult.AppendElement(mPuppetController[i]);
-  }
-}
-
-void VRSystemManagerPuppet::ScanForControllers() {
-  // We make sure VRSystemManagerPuppet has two controllers
-  // for each display
-  const uint32_t newControllerCount = mPuppetHMDs.Length() * 2;
-
-  if (newControllerCount != mControllerCount) {
-    RemoveControllers();
-
-    // Re-adding controllers to VRControllerManager.
-    for (const auto& display : mPuppetHMDs) {
-      uint32_t displayID = display->GetDisplayInfo().GetDisplayID();
-      for (uint32_t i = 0; i < 2; i++) {
-        dom::GamepadHand hand =
-            (i % 2) ? dom::GamepadHand::Right : dom::GamepadHand::Left;
-        RefPtr<VRControllerPuppet> puppetController;
-        puppetController = new VRControllerPuppet(hand, displayID);
-        mPuppetController.AppendElement(puppetController);
-
-        // Not already present, add it.
-        AddGamepad(puppetController->GetControllerInfo());
-        ++mControllerCount;
-      }
-    }
-  }
-}
-
-void VRSystemManagerPuppet::RemoveControllers() {
-  // controller count is changed, removing the existing gamepads first.
-  for (uint32_t i = 0; i < mPuppetController.Length(); ++i) {
-    RemoveGamepad(i);
-  }
-  mPuppetController.Clear();
-  mControllerCount = 0;
-}
deleted file mode 100644
--- a/gfx/vr/gfxVRPuppet.h
+++ /dev/null
@@ -1,163 +0,0 @@
-/* -*- 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 GFX_VR_PUPPET_H
-#define GFX_VR_PUPPET_H
-
-#include "nsTArray.h"
-#include "mozilla/RefPtr.h"
-#include "nsRefPtrHashtable.h"
-
-#include "gfxVR.h"
-#include "VRDisplayLocal.h"
-
-#if defined(XP_WIN)
-#  include "CompositorD3D11.h"
-#endif
-
-#if defined(XP_MACOSX)
-class MacIOSurface;
-#endif
-namespace mozilla {
-namespace layers {
-struct VertexShaderConstants;
-struct PixelShaderConstants;
-}  // namespace layers
-namespace gfx {
-namespace impl {
-
-class VRDisplayPuppet : public VRDisplayLocal {
- public:
-  void SetDisplayInfo(const VRDisplayInfo& aDisplayInfo);
-  void SetSensorState(const VRHMDSensorState& aSensorState);
-  void ZeroSensor() override;
-
- protected:
-  VRHMDSensorState& GetSensorState() override;
-  void StartPresentation() override;
-  void StopPresentation() override;
-#if defined(XP_WIN)
-  virtual bool SubmitFrame(ID3D11Texture2D* aSource, const IntSize& aSize,
-                           const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect) override;
-#elif defined(XP_MACOSX)
-  virtual bool SubmitFrame(MacIOSurface* aMacIOSurface, const IntSize& aSize,
-                           const gfx::Rect& aLeftEyeRect,
-                           const gfx::Rect& aRightEyeRect) override;
-#elif defined(MOZ_WIDGET_ANDROID)
-  virtual bool SubmitFrame(
-      const mozilla::layers::SurfaceTextureDescriptor& aDescriptor,
-      const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) override;
-#endif
-
- public:
-  explicit VRDisplayPuppet();
-  void Refresh();
-
- protected:
-  virtual ~VRDisplayPuppet();
-  void Destroy();
-
-  bool mIsPresenting;
-
- private:
-#if defined(XP_WIN)
-  bool UpdateConstantBuffers();
-
-  ID3D11VertexShader* mQuadVS;
-  ID3D11PixelShader* mQuadPS;
-  RefPtr<ID3D11SamplerState> mLinearSamplerState;
-  layers::VertexShaderConstants mVSConstants;
-  layers::PixelShaderConstants mPSConstants;
-  RefPtr<ID3D11Buffer> mVSConstantBuffer;
-  RefPtr<ID3D11Buffer> mPSConstantBuffer;
-  RefPtr<ID3D11Buffer> mVertexBuffer;
-  RefPtr<ID3D11InputLayout> mInputLayout;
-#endif
-
-  VRHMDSensorState mSensorState;
-};
-
-class VRControllerPuppet : public VRControllerHost {
- public:
-  explicit VRControllerPuppet(dom::GamepadHand aHand, uint32_t aDisplayID);
-  void SetButtonPressState(uint32_t aButton, bool aPressed);
-  uint64_t GetButtonPressState();
-  void SetButtonTouchState(uint32_t aButton, bool aTouched);
-  uint64_t GetButtonTouchState();
-  void SetAxisMoveState(uint32_t aAxis, double aValue);
-  double GetAxisMoveState(uint32_t aAxis);
-  void SetPoseMoveState(const dom::GamepadPoseState& aPose);
-  const dom::GamepadPoseState& GetPoseMoveState();
-  float GetAxisMove(uint32_t aAxis);
-  void SetAxisMove(uint32_t aAxis, float aValue);
-
- protected:
-  virtual ~VRControllerPuppet();
-
- private:
-  uint64_t mButtonPressState;
-  uint64_t mButtonTouchState;
-  float mAxisMoveState[3];
-  dom::GamepadPoseState mPoseState;
-};
-
-}  // namespace impl
-
-class VRSystemManagerPuppet : public VRSystemManager {
- public:
-  static already_AddRefed<VRSystemManagerPuppet> Create();
-  uint32_t CreateTestDisplay();
-  void ClearTestDisplays();
-  void SetPuppetDisplayInfo(const uint32_t& aDeviceID,
-                            const VRDisplayInfo& aDisplayInfo);
-  void SetPuppetDisplaySensorState(const uint32_t& aDeviceID,
-                                   const VRHMDSensorState& aSensorState);
-
-  void Destroy() override;
-  void Shutdown() override;
-  void Enumerate() override;
-  void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
-  bool GetIsPresenting() override;
-  void HandleInput() override;
-  void GetControllers(
-      nsTArray<RefPtr<VRControllerHost>>& aControllerResult) override;
-  void ScanForControllers() override;
-  void RemoveControllers() override;
-  void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
-                     double aIntensity, double aDuration,
-                     const VRManagerPromise& aPromise) override;
-  void StopVibrateHaptic(uint32_t aControllerIdx) override;
-  void NotifyVSync() override;
-  void Run10msTasks() override;
-
- protected:
-  VRSystemManagerPuppet();
-
- private:
-  void HandleButtonPress(uint32_t aControllerIdx, uint32_t aButton,
-                         uint64_t aButtonMask, uint64_t aButtonPressed,
-                         uint64_t aButtonTouched);
-  void HandleAxisMove(uint32_t aControllerIndex, uint32_t aAxis, float aValue);
-  void HandlePoseTracking(uint32_t aControllerIndex,
-                          const dom::GamepadPoseState& aPose,
-                          VRControllerHost* aController);
-
-  // Enumerated puppet hardware devices, as seen by Web APIs:
-  nsTArray<RefPtr<impl::VRDisplayPuppet>> mPuppetHMDs;
-  nsTArray<RefPtr<impl::VRControllerPuppet>> mPuppetController;
-
-  // Emulated hardware state, persistent through VRSystemManager::Shutdown():
-  static const uint32_t kMaxPuppetDisplays = 5;
-  uint32_t mPuppetDisplayCount;
-  VRDisplayInfo mPuppetDisplayInfo[kMaxPuppetDisplays];
-  VRHMDSensorState mPuppetDisplaySensorState[kMaxPuppetDisplays];
-};
-
-}  // namespace gfx
-}  // namespace mozilla
-
-#endif /* GFX_VR_PUPPET_H*/
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -36,59 +36,41 @@ sync protocol PVRManager
 
 parent:
   async PVRLayer(uint32_t aDisplayID, uint32_t aGroup);
 
   // (Re)Enumerate VR Displays.  An updated list of VR displays will be returned
   // asynchronously to children via UpdateDisplayInfo.
   async RefreshDisplays();
 
-  // Reset the sensor of the display identified by aDisplayID so that the current
-  // sensor state is the "Zero" position.
-  async ResetSensor(uint32_t aDisplayID);
-
   async SetGroupMask(uint32_t aDisplayID, uint32_t aGroupMask);
   async SetHaveEventListener(bool aHaveEventListener);
 
   async ControllerListenerAdded();
   async ControllerListenerRemoved();
   async VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                       double aIntensity, double aDuration, uint32_t aPromiseID);
   async StopVibrateHaptic(uint32_t aControllerIdx);
-
-  async CreateVRTestSystem();
-  async CreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID);
-  async CreateVRServiceTestController(nsCString aID, uint32_t aPromiseID);
-  async SetDisplayInfoToMockDisplay(uint32_t aDeviceID, VRDisplayInfo aDisplayInfo);
-  async SetSensorStateToMockDisplay(uint32_t aDeviceID, VRHMDSensorState aSensorState);
-
-  async NewButtonEventToMockController(uint32_t aDeviceID, long aButton,
-                                       bool aPressed);
-  async NewAxisMoveEventToMockController(uint32_t aDeviceID, long aAxis,
-                                         double aValue);
-  async NewPoseMoveToMockController(uint32_t aDeviceID, GamepadPoseState aPose);
   async StartVRNavigation(uint32_t aDeviceID);
   async StopVRNavigation(uint32_t aDeviceID, TimeDuration aDuration);
   async StartActivity();
   async StopActivity();
 
+  async RunPuppet(uint64_t[] buffer);
+  async ResetPuppet();
+
 child:
   // Notify children of updated VR display enumeration and details.  This will
   // be sent to all children when the parent receives RefreshDisplays, even
   // if no changes have been detected.  This ensures that Promises exposed
   // through DOM calls are always resolved.
   async UpdateDisplayInfo(VRDisplayInfo[] aDisplayUpdates);
 
-  async DispatchSubmitFrameResult(uint32_t aDisplayID, VRSubmitFrameResultInfo aResult);
-  async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
   async ReplyGamepadVibrateHaptic(uint32_t aPromiseID);
-
-  async ReplyCreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID,
-                                        uint32_t aDeviceID);
-  async ReplyCreateVRServiceTestController(nsCString aID, uint32_t aPromiseID,
-                                           uint32_t aDeviceID);
+  async NotifyPuppetCommandBufferCompleted(bool aSuccess);
+  async NotifyPuppetResetComplete();
 
   async __delete__();
 
 };
 
 } // gfx
 } // mozilla
--- a/gfx/vr/ipc/VRChild.cpp
+++ b/gfx/vr/ipc/VRChild.cpp
@@ -4,16 +4,17 @@
  * 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 "VRChild.h"
 #include "VRProcessParent.h"
 #include "gfxConfig.h"
 
 #include "mozilla/gfx/gfxVars.h"
+#include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/VsyncDispatcher.h"
 #include "mozilla/dom/MemoryReportRequest.h"
 #include "mozilla/ipc/CrashReporterHost.h"
 
 namespace mozilla {
 namespace gfx {
--- a/gfx/vr/ipc/VRGPUChild.cpp
+++ b/gfx/vr/ipc/VRGPUChild.cpp
@@ -1,15 +1,16 @@
 /* -*- 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 "VRGPUChild.h"
+#include "mozilla/layers/CompositorThread.h"
 
 namespace mozilla {
 namespace gfx {
 
 static StaticRefPtr<VRGPUChild> sVRGPUChildSingleton;
 
 /* static */
 bool VRGPUChild::InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint) {
@@ -48,17 +49,17 @@ void VRGPUChild::Shutdown() {
   if (sVRGPUChildSingleton && !sVRGPUChildSingleton->IsClosed()) {
     sVRGPUChildSingleton->Close();
   }
   sVRGPUChildSingleton = nullptr;
 }
 
 void VRGPUChild::ActorDestroy(ActorDestroyReason aWhy) {
   VRManager* vm = VRManager::Get();
-  CompositorThreadHolder::Loop()->PostTask(
+  mozilla::layers::CompositorThreadHolder::Loop()->PostTask(
       NewRunnableMethod("VRGPUChild::ActorDestroy", vm, &VRManager::Shutdown));
 
   mClosed = true;
 }
 
 bool VRGPUChild::IsClosed() { return mClosed; }
 
 }  // namespace gfx
--- a/gfx/vr/ipc/VRLayerParent.cpp
+++ b/gfx/vr/ipc/VRLayerParent.cpp
@@ -1,17 +1,16 @@
 /* -*- 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 "VRLayerParent.h"
 #include "mozilla/Unused.h"
-#include "VRDisplayHost.h"
 #include "mozilla/layers/CompositorThread.h"
 
 namespace mozilla {
 using namespace layers;
 namespace gfx {
 
 VRLayerParent::VRLayerParent(uint32_t aVRDisplayID, const uint32_t aGroup)
     : mIPCOpen(true), mVRDisplayID(aVRDisplayID), mGroup(aGroup) {}
@@ -23,39 +22,32 @@ mozilla::ipc::IPCResult VRLayerParent::R
   return IPC_OK();
 }
 
 void VRLayerParent::ActorDestroy(ActorDestroyReason aWhy) { mIPCOpen = false; }
 
 void VRLayerParent::Destroy() {
   if (mVRDisplayID) {
     VRManager* vm = VRManager::Get();
-    RefPtr<gfx::VRDisplayHost> display = vm->GetDisplay(mVRDisplayID);
-    if (display) {
-      display->RemoveLayer(this);
-    }
+    vm->RemoveLayer(this);
     // 0 will never be a valid VRDisplayID; we can use it to indicate that
     // we are destroyed and no longer associated with a display.
     mVRDisplayID = 0;
   }
 
   if (mIPCOpen) {
     Unused << PVRLayerParent::Send__delete__(this);
   }
 }
 
 mozilla::ipc::IPCResult VRLayerParent::RecvSubmitFrame(
     const layers::SurfaceDescriptor& aTexture, const uint64_t& aFrameId,
     const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) {
   if (mVRDisplayID) {
     VRManager* vm = VRManager::Get();
-    RefPtr<VRDisplayHost> display = vm->GetDisplay(mVRDisplayID);
-    if (display) {
-      display->SubmitFrame(this, aTexture, aFrameId, aLeftEyeRect,
-                           aRightEyeRect);
-    }
+    vm->SubmitFrame(this, aTexture, aFrameId, aLeftEyeRect, aRightEyeRect);
   }
 
   return IPC_OK();
 }
 
 }  // namespace gfx
 }  // namespace mozilla
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -12,17 +12,16 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/layers/CompositorThread.h"  // for CompositorThread
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/VREventObserver.h"
 #include "mozilla/dom/WindowBinding.h"  // for FrameRequestCallback
 #include "mozilla/dom/ContentChild.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/GamepadManager.h"
-#include "mozilla/dom/VRServiceTest.h"
 #include "mozilla/layers/SyncObject.h"
 
 using namespace mozilla::dom;
 
 namespace {
 const nsTArray<RefPtr<dom::VREventObserver>>::index_type kNoIndex =
     nsTArray<RefPtr<dom::VREventObserver>>::NoIndex;
 }  // namespace
@@ -34,20 +33,17 @@ static StaticRefPtr<VRManagerChild> sVRM
 static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton;
 
 void ReleaseVRManagerParentSingleton() { sVRManagerParentSingleton = nullptr; }
 
 VRManagerChild::VRManagerChild()
     : mDisplaysInitialized(false),
       mMessageLoop(MessageLoop::current()),
       mFrameRequestCallbackCounter(0),
-      mBackend(layers::LayersBackend::LAYERS_NONE),
-      mPromiseID(0),
-      mVRMockDisplay(nullptr),
-      mLastControllerState{} {
+      mBackend(layers::LayersBackend::LAYERS_NONE) {
   MOZ_ASSERT(NS_IsMainThread());
 
   mStartTimeStamp = TimeStamp::Now();
 }
 
 VRManagerChild::~VRManagerChild() { MOZ_ASSERT(NS_IsMainThread()); }
 
 /*static*/
@@ -237,44 +233,76 @@ mozilla::ipc::IPCResult VRManagerChild::
       continue;
     }
     nav->NotifyVRDisplaysUpdated();
   }
   mNavigatorCallbacks.Clear();
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetCommandBufferCompleted(
+    bool aSuccess) {
+  RefPtr<dom::Promise> promise = mRunPuppetPromise;
+  mRunPuppetPromise = nullptr;
+  if (aSuccess) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+  } else {
+    promise->MaybeRejectWithUndefined();
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetResetComplete() {
+  nsTArray<RefPtr<dom::Promise>> promises;
+  promises.AppendElements(mResetPuppetPromises);
+  mResetPuppetPromises.Clear();
+  for (const auto& promise : promises) {
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+  }
+  return IPC_OK();
+}
+
+void VRManagerChild::RunPuppet(const InfallibleTArray<uint64_t>& aBuffer,
+                               dom::Promise* aPromise, ErrorResult& aRv) {
+  if (mRunPuppetPromise) {
+    // We only allow one puppet script to run simultaneously.
+    // The prior promise must be resolved before running a new
+    // script.
+    aRv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+  if (!SendRunPuppet(aBuffer)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+  mRunPuppetPromise = aPromise;
+}
+
+void VRManagerChild::ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv) {
+  if (!SendResetPuppet()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+  mResetPuppetPromises.AppendElement(aPromise);
+}
+
 bool VRManagerChild::GetVRDisplays(
     nsTArray<RefPtr<VRDisplayClient>>& aDisplays) {
   aDisplays = mDisplays;
   return true;
 }
 
 bool VRManagerChild::RefreshVRDisplaysWithCallback(uint64_t aWindowId) {
   bool success = SendRefreshDisplays();
   if (success) {
     mNavigatorCallbacks.AppendElement(aWindowId);
   }
   return success;
 }
 
-void VRManagerChild::CreateVRServiceTestDisplay(const nsCString& aID,
-                                                dom::Promise* aPromise) {
-  SendCreateVRServiceTestDisplay(aID, mPromiseID);
-  mPromiseList.Put(mPromiseID, aPromise);
-  ++mPromiseID;
-}
-
-void VRManagerChild::CreateVRServiceTestController(const nsCString& aID,
-                                                   dom::Promise* aPromise) {
-  SendCreateVRServiceTestController(aID, mPromiseID);
-  mPromiseList.Put(mPromiseID, aPromise);
-  ++mPromiseID;
-}
-
 PVRLayerChild* VRManagerChild::CreateVRLayer(uint32_t aDisplayID,
                                              nsIEventTarget* aTarget,
                                              uint32_t aGroup) {
   PVRLayerChild* vrLayerChild = AllocPVRLayerChild(aDisplayID, aGroup);
   // Do the DOM labeling.
   if (aTarget) {
     SetEventTargetForActor(vrLayerChild, aTarget);
     MOZ_ASSERT(vrLayerChild->GetActorEventTarget());
@@ -319,68 +347,16 @@ nsresult VRManagerChild::ScheduleFrameRe
   return NS_OK;
 }
 
 void VRManagerChild::CancelFrameRequestCallback(int32_t aHandle) {
   // mFrameRequestCallbacks is stored sorted by handle
   mFrameRequestCallbacks.RemoveElementSorted(aHandle);
 }
 
-mozilla::ipc::IPCResult VRManagerChild::RecvGamepadUpdate(
-    const GamepadChangeEvent& aGamepadEvent) {
-  // VRManagerChild could be at other processes, but GamepadManager
-  // only exists at the content process or the same process
-  // in non-e10s mode.
-  MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess());
-
-  RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
-  if (gamepadManager) {
-    gamepadManager->Update(aGamepadEvent);
-  }
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult VRManagerChild::RecvReplyCreateVRServiceTestDisplay(
-    const nsCString& aID, const uint32_t& aPromiseID,
-    const uint32_t& aDeviceID) {
-  RefPtr<dom::Promise> p;
-  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
-    MOZ_CRASH("We should always have a promise.");
-  }
-
-  // We only allow one VRMockDisplay in VR tests.
-  if (!mVRMockDisplay) {
-    mVRMockDisplay = new VRMockDisplay(aID, aDeviceID);
-  }
-  p->MaybeResolve(mVRMockDisplay);
-  mPromiseList.Remove(aPromiseID);
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult VRManagerChild::RecvReplyCreateVRServiceTestController(
-    const nsCString& aID, const uint32_t& aPromiseID,
-    const uint32_t& aDeviceID) {
-  RefPtr<dom::Promise> p;
-  if (!mPromiseList.Get(aPromiseID, getter_AddRefs(p))) {
-    MOZ_CRASH("We should always have a promise.");
-  }
-
-  if (aDeviceID == 0) {
-    // A value of 0 indicates that the controller could not
-    // be created.  Most likely due to having no VR display
-    // to associate it with.
-    p->MaybeRejectWithUndefined();
-  } else {
-    p->MaybeResolve(new VRMockController(aID, aDeviceID));
-  }
-  mPromiseList.Remove(aPromiseID);
-  return IPC_OK();
-}
-
 void VRManagerChild::RunFrameRequestCallbacks() {
   AUTO_PROFILER_TRACING("VR", "RunFrameRequestCallbacks", GRAPHICS);
 
   TimeStamp nowTime = TimeStamp::Now();
   mozilla::TimeDuration duration = nowTime - mStartTimeStamp;
   DOMHighResTimeStamp timeStamp = duration.ToMilliseconds();
 
   nsTArray<FrameRequest> callbacks;
@@ -566,23 +542,10 @@ mozilla::ipc::IPCResult VRManagerChild::
     MOZ_CRASH("We should always have a promise.");
   }
 
   p->MaybeResolve(true);
   mGamepadPromiseList.Remove(aPromiseID);
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult VRManagerChild::RecvDispatchSubmitFrameResult(
-    const uint32_t& aDisplayID, const VRSubmitFrameResultInfo& aResult) {
-  nsTArray<RefPtr<VRDisplayClient>> displays;
-  displays = mDisplays;
-  for (auto& display : displays) {
-    if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) {
-      display->UpdateSubmitFrameResult(aResult);
-    }
-  }
-
-  return IPC_OK();
-}
-
 }  // namespace gfx
 }  // namespace mozilla
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -18,17 +18,16 @@
 
 namespace mozilla {
 namespace dom {
 class Promise;
 class GamepadManager;
 class Navigator;
 class VRDisplay;
 class VREventObserver;
-class VRMockDisplay;
 }  // namespace dom
 namespace layers {
 class SyncObjectClient;
 }
 namespace gfx {
 class VRLayerChild;
 class VRDisplayClient;
 
@@ -46,20 +45,16 @@ class VRManagerChild : public PVRManager
   void RemoveListener(dom::VREventObserver* aObserver);
   void StartActivity();
   void StopActivity();
 
   bool GetVRDisplays(nsTArray<RefPtr<VRDisplayClient>>& aDisplays);
   bool RefreshVRDisplaysWithCallback(uint64_t aWindowId);
   void AddPromise(const uint32_t& aID, dom::Promise* aPromise);
 
-  void CreateVRServiceTestDisplay(const nsCString& aID, dom::Promise* aPromise);
-  void CreateVRServiceTestController(const nsCString& aID,
-                                     dom::Promise* aPromise);
-
   static void InitSameProcess();
   static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static bool ReinitForContent(Endpoint<PVRManagerChild>&& aEndpoint);
   static void ShutDown();
 
   static bool IsCreated();
 
@@ -84,45 +79,43 @@ class VRManagerChild : public PVRManager
   void FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayConnectEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID);
   void FireDOMVRDisplayConnectEventsForLoad(dom::VREventObserver* aObserver);
 
   virtual void HandleFatalError(const char* aMsg) const override;
 
+  void RunPuppet(const InfallibleTArray<uint64_t>& aBuffer,
+                 dom::Promise* aPromise, ErrorResult& aRv);
+  void ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv);
+
  protected:
   explicit VRManagerChild();
   ~VRManagerChild();
   void Destroy();
   static void DeferredDestroy(RefPtr<VRManagerChild> aVRManagerChild);
 
   PVRLayerChild* AllocPVRLayerChild(const uint32_t& aDisplayID,
                                     const uint32_t& aGroup);
   bool DeallocPVRLayerChild(PVRLayerChild* actor);
 
   // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can mark ipdl-generated things as
   // MOZ_CAN_RUN_SCRIPT.
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   mozilla::ipc::IPCResult RecvUpdateDisplayInfo(
       nsTArray<VRDisplayInfo>&& aDisplayUpdates);
-
-  mozilla::ipc::IPCResult RecvDispatchSubmitFrameResult(
-      const uint32_t& aDisplayID, const VRSubmitFrameResultInfo& aResult);
-  mozilla::ipc::IPCResult RecvGamepadUpdate(
-      const GamepadChangeEvent& aGamepadEvent);
   mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic(
       const uint32_t& aPromiseID);
 
-  mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestDisplay(
-      const nsCString& aID, const uint32_t& aPromiseID,
-      const uint32_t& aDeviceID);
-  mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestController(
-      const nsCString& aID, const uint32_t& aPromiseID,
-      const uint32_t& aDeviceID);
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
+  mozilla::ipc::IPCResult RecvNotifyPuppetCommandBufferCompleted(bool aSuccess);
+  MOZ_CAN_RUN_SCRIPT_BOUNDARY
+  mozilla::ipc::IPCResult RecvNotifyPuppetResetComplete();
+
   bool IsSameProcess() const { return OtherPid() == base::GetCurrentProcId(); }
 
  private:
   void FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID);
   void FireDOMVRDisplayUnmountedEventInternal(uint32_t aDisplayID);
   void FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID);
   void FireDOMVRDisplayDisconnectEventInternal(uint32_t aDisplayID);
   void FireDOMVRDisplayPresentChangeEventInternal(uint32_t aDisplayID);
@@ -145,20 +138,18 @@ class VRManagerChild : public PVRManager
   int32_t mFrameRequestCallbackCounter;
   mozilla::TimeStamp mStartTimeStamp;
 
   nsTArray<RefPtr<dom::VREventObserver>> mListeners;
 
   layers::LayersBackend mBackend;
   RefPtr<layers::SyncObjectClient> mSyncObject;
   nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mGamepadPromiseList;
-  uint32_t mPromiseID;
-  nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mPromiseList;
-  RefPtr<dom::VRMockDisplay> mVRMockDisplay;
-  VRControllerState mLastControllerState[kVRControllerMaxCount];
+  RefPtr<dom::Promise> mRunPuppetPromise;
+  nsTArray<RefPtr<dom::Promise>> mResetPuppetPromises;
 
   DISALLOW_COPY_AND_ASSIGN(VRManagerChild);
 };
 
 }  // namespace gfx
 }  // namespace mozilla
 
 #endif  // MOZILLA_GFX_VR_VRMANAGERCHILD_H
--- a/gfx/vr/ipc/VRManagerParent.cpp
+++ b/gfx/vr/ipc/VRManagerParent.cpp
@@ -1,33 +1,32 @@
 /* -*- 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 "VRManagerParent.h"
+
 #include "ipc/VRLayerParent.h"
 #include "mozilla/gfx/PVRManagerParent.h"
 #include "mozilla/ipc/ProtocolTypes.h"
 #include "mozilla/ipc/ProtocolUtils.h"  // for IToplevelProtocol
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/Unused.h"
 #include "VRManager.h"
 #include "VRThread.h"
-#include "gfxVRPuppet.h"
 
 namespace mozilla {
 using namespace layers;
 namespace gfx {
 
 VRManagerParent::VRManagerParent(ProcessId aChildProcessId,
                                  bool aIsContentChild)
-    : mControllerTestID(1),
-      mHaveEventListener(false),
+    : mHaveEventListener(false),
       mHaveControllerListener(false),
       mIsContentChild(aIsContentChild),
       mVRActiveStatus(true) {
   MOZ_COUNT_CTOR(VRManagerParent);
   MOZ_ASSERT(NS_IsMainThread());
 
   SetOtherProcessId(aChildProcessId);
 }
@@ -38,20 +37,17 @@ VRManagerParent::~VRManagerParent() {
   MOZ_COUNT_DTOR(VRManagerParent);
 }
 
 PVRLayerParent* VRManagerParent::AllocPVRLayerParent(const uint32_t& aDisplayID,
                                                      const uint32_t& aGroup) {
   RefPtr<VRLayerParent> layer;
   layer = new VRLayerParent(aDisplayID, aGroup);
   VRManager* vm = VRManager::Get();
-  RefPtr<gfx::VRDisplayHost> display = vm->GetDisplay(aDisplayID);
-  if (display) {
-    display->AddLayer(layer);
-  }
+  vm->AddLayer(layer);
   return layer.forget().take();
 }
 
 bool VRManagerParent::DeallocPVRLayerParent(PVRLayerParent* actor) {
   delete actor;
   return true;
 }
 
@@ -150,34 +146,20 @@ mozilla::ipc::IPCResult VRManagerParent:
   // to ensure that the promise returned by Navigator.GetVRDevices
   // can resolve even if there are no changes to the VR Displays.
   VRManager* vm = VRManager::Get();
   vm->RefreshVRDisplays(true);
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult VRManagerParent::RecvResetSensor(
-    const uint32_t& aDisplayID) {
-  VRManager* vm = VRManager::Get();
-  RefPtr<gfx::VRDisplayHost> display = vm->GetDisplay(aDisplayID);
-  if (display != nullptr) {
-    display->ZeroSensor();
-  }
-
-  return IPC_OK();
-}
-
 mozilla::ipc::IPCResult VRManagerParent::RecvSetGroupMask(
     const uint32_t& aDisplayID, const uint32_t& aGroupMask) {
   VRManager* vm = VRManager::Get();
-  RefPtr<gfx::VRDisplayHost> display = vm->GetDisplay(aDisplayID);
-  if (display != nullptr) {
-    display->SetGroupMask(aGroupMask);
-  }
+  vm->SetGroupMask(aGroupMask);
   return IPC_OK();
 }
 
 bool VRManagerParent::HaveEventListener() { return mHaveEventListener; }
 
 bool VRManagerParent::HaveControllerListener() {
   return mHaveControllerListener;
 }
@@ -188,178 +170,53 @@ mozilla::ipc::IPCResult VRManagerParent:
     const bool& aHaveEventListener) {
   mHaveEventListener = aHaveEventListener;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VRManagerParent::RecvControllerListenerAdded() {
   // Force update the available controllers for GamepadManager,
   VRManager* vm = VRManager::Get();
-  vm->RemoveControllers();
+  vm->StopAllHaptics();
   mHaveControllerListener = true;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VRManagerParent::RecvControllerListenerRemoved() {
   mHaveControllerListener = false;
   VRManager* vm = VRManager::Get();
-  vm->RemoveControllers();
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult VRManagerParent::RecvCreateVRTestSystem() {
-  VRManager* vm = VRManager::Get();
-  vm->CreateVRTestSystem();
-  // The mControllerTestID is 1 based
-  mControllerTestID = 1;
-  mVRControllerTests.Clear();
+  vm->StopAllHaptics();
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult VRManagerParent::RecvCreateVRServiceTestDisplay(
-    const nsCString& aID, const uint32_t& aPromiseID) {
-  VRManager* vm = VRManager::Get();
-  VRSystemManagerPuppet* puppetManager = vm->GetPuppetManager();
-  uint32_t deviceID = puppetManager->CreateTestDisplay();
-
-  if (SendReplyCreateVRServiceTestDisplay(aID, aPromiseID, deviceID)) {
-    return IPC_OK();
-  }
-
-  return IPC_FAIL(this, "SendReplyCreateVRServiceTestController fail");
-}
-
-mozilla::ipc::IPCResult VRManagerParent::RecvCreateVRServiceTestController(
-    const nsCString& aID, const uint32_t& aPromiseID) {
-  uint32_t controllerIdx = 1;  // ID's are 1 based
-  nsTArray<VRControllerInfo> controllerInfoArray;
-  impl::VRControllerPuppet* controllerPuppet = nullptr;
+mozilla::ipc::IPCResult VRManagerParent::RecvRunPuppet(
+    const InfallibleTArray<uint64_t>& aBuffer) {
+#if defined(MOZ_WIDGET_ANDROID)
+  // Not yet implemented for Android / GeckoView
+  // See Bug 1555192
+  Unused << SendNotifyPuppetCommandBufferCompleted(false);
+#else
   VRManager* vm = VRManager::Get();
-
-  /**
-   * The controller is created asynchronously.
-   * We will wait up to kMaxControllerCreationTime milliseconds before
-   * assuming that the controller will never be created.
-   */
-  const int kMaxControllerCreationTime = 1000;
-  /**
-   * min(100ms, kVRIdleTaskInterval) * 10 as a very
-   * pessimistic estimation of the maximum duration possible.
-   * It's possible that the IPC message queues could be so busy
-   * that this time is elapsed while still succeeding to create
-   * the controllers; however, in this case the browser would be
-   * locking up for more than a second at a time and something else
-   * has gone horribly wrong.
-   */
-  const int kTestInterval = 10;
-  /**
-   * We will keep checking every kTestInterval milliseconds until
-   * we see the controllers or kMaxControllerCreationTime milliseconds
-   * have elapsed and we will give up.
-   */
-
-  int testDuration = 0;
-  while (!controllerPuppet && testDuration < kMaxControllerCreationTime) {
-    testDuration += kTestInterval;
-#ifdef XP_WIN
-    Sleep(kTestInterval);
-#else
-    sleep(kTestInterval);
-#endif
-
-    // Get VRControllerPuppet from VRManager
-    vm->GetVRControllerInfo(controllerInfoArray);
-    for (auto& controllerInfo : controllerInfoArray) {
-      if (controllerInfo.GetType() == VRDeviceType::Puppet) {
-        if (controllerIdx == mControllerTestID) {
-          controllerPuppet = static_cast<impl::VRControllerPuppet*>(
-              vm->GetController(controllerInfo.GetControllerID()).get());
-          break;
-        }
-        ++controllerIdx;
-      }
-    }
+  if (!vm->RunPuppet(aBuffer, this)) {
+    // We have immediately failed, need to resolve the
+    // promise right away
+    Unused << SendNotifyPuppetCommandBufferCompleted(false);
   }
-
-  // We might not have a controllerPuppet if the test did
-  // not create a VR display first.
-  if (!controllerPuppet) {
-    // We send a device ID of "0" to indicate failure
-    if (SendReplyCreateVRServiceTestController(aID, aPromiseID, 0)) {
-      return IPC_OK();
-    }
-  } else {
-    if (!mVRControllerTests.Get(mControllerTestID, nullptr)) {
-      mVRControllerTests.Put(mControllerTestID, controllerPuppet);
-    }
-
-    if (SendReplyCreateVRServiceTestController(aID, aPromiseID,
-                                               mControllerTestID)) {
-      ++mControllerTestID;
-      return IPC_OK();
-    }
-  }
-
-  return IPC_FAIL(this, "SendReplyCreateVRServiceTestController fail");
-}
-
-mozilla::ipc::IPCResult VRManagerParent::RecvSetDisplayInfoToMockDisplay(
-    const uint32_t& aDeviceID, const VRDisplayInfo& aDisplayInfo) {
-  VRManager* vm = VRManager::Get();
-  VRSystemManagerPuppet* puppetManager = vm->GetPuppetManager();
-  puppetManager->SetPuppetDisplayInfo(aDeviceID, aDisplayInfo);
+#endif  // defined(MOZ_WIDGET_ANDROID)
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult VRManagerParent::RecvSetSensorStateToMockDisplay(
-    const uint32_t& aDeviceID, const VRHMDSensorState& aSensorState) {
+mozilla::ipc::IPCResult VRManagerParent::RecvResetPuppet() {
+#if defined(MOZ_WIDGET_ANDROID)
+  // Not yet implemented for Android / GeckoView
+  // See Bug 1555192
+#else
   VRManager* vm = VRManager::Get();
-  VRSystemManagerPuppet* puppetManager = vm->GetPuppetManager();
-  puppetManager->SetPuppetDisplaySensorState(aDeviceID, aSensorState);
-  return IPC_OK();
-}
-
-already_AddRefed<impl::VRControllerPuppet> VRManagerParent::GetControllerPuppet(
-    uint32_t aDeviceID) {
-  // aDeviceID for controllers start at 1 and are
-  // used as a key to mVRControllerTests
-  MOZ_ASSERT(aDeviceID > 0);
-  RefPtr<impl::VRControllerPuppet> controllerPuppet;
-  mVRControllerTests.Get(aDeviceID, getter_AddRefs(controllerPuppet));
-  MOZ_ASSERT(controllerPuppet);
-  return controllerPuppet.forget();
-}
-
-mozilla::ipc::IPCResult VRManagerParent::RecvNewButtonEventToMockController(
-    const uint32_t& aDeviceID, const long& aButton, const bool& aPressed) {
-  RefPtr<impl::VRControllerPuppet> controllerPuppet =
-      GetControllerPuppet(aDeviceID);
-  if (controllerPuppet) {
-    controllerPuppet->SetButtonPressState(aButton, aPressed);
-  }
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult VRManagerParent::RecvNewAxisMoveEventToMockController(
-    const uint32_t& aDeviceID, const long& aAxis, const double& aValue) {
-  RefPtr<impl::VRControllerPuppet> controllerPuppet =
-      GetControllerPuppet(aDeviceID);
-  if (controllerPuppet) {
-    controllerPuppet->SetAxisMoveState(aAxis, aValue);
-  }
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult VRManagerParent::RecvNewPoseMoveToMockController(
-    const uint32_t& aDeviceID, const GamepadPoseState& pose) {
-  RefPtr<impl::VRControllerPuppet> controllerPuppet =
-      GetControllerPuppet(aDeviceID);
-  if (controllerPuppet) {
-    controllerPuppet->SetPoseMoveState(pose);
-  }
+  vm->ResetPuppet(this);
+#endif  // defined(MOZ_WIDGET_ANDROID)
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult VRManagerParent::RecvVibrateHaptic(
     const uint32_t& aControllerIdx, const uint32_t& aHapticIndex,
     const double& aIntensity, const double& aDuration,
     const uint32_t& aPromiseID) {
   VRManager* vm = VRManager::Get();
@@ -372,27 +229,16 @@ mozilla::ipc::IPCResult VRManagerParent:
 
 mozilla::ipc::IPCResult VRManagerParent::RecvStopVibrateHaptic(
     const uint32_t& aControllerIdx) {
   VRManager* vm = VRManager::Get();
   vm->StopVibrateHaptic(aControllerIdx);
   return IPC_OK();
 }
 
-bool VRManagerParent::SendGamepadUpdate(
-    const GamepadChangeEvent& aGamepadEvent) {
-  // GamepadManager only exists at the content process
-  // or the same process in non-e10s mode.
-  if (mHaveControllerListener && (mIsContentChild || IsSameProcess())) {
-    return PVRManagerParent::SendGamepadUpdate(aGamepadEvent);
-  }
-
-  return true;
-}
-
 bool VRManagerParent::SendReplyGamepadVibrateHaptic(
     const uint32_t& aPromiseID) {
   // GamepadManager only exists at the content process
   // or the same process in non-e10s mode.
   if (mHaveControllerListener && (mIsContentChild || IsSameProcess())) {
     return PVRManagerParent::SendReplyGamepadVibrateHaptic(aPromiseID);
   }
 
--- a/gfx/vr/ipc/VRManagerParent.h
+++ b/gfx/vr/ipc/VRManagerParent.h
@@ -16,108 +16,85 @@
 #include "gfxVR.h"                         // for VRFieldOfView
 
 namespace mozilla {
 using namespace layers;
 namespace gfx {
 
 class VRManager;
 
-namespace impl {
-class VRDisplayPuppet;
-class VRControllerPuppet;
-}  // namespace impl
-
 class VRManagerParent final : public PVRManagerParent {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRManagerParent);
 
   friend class PVRManagerParent;
 
  public:
   explicit VRManagerParent(ProcessId aChildProcessId, bool aIsContentChild);
 
   static VRManagerParent* CreateSameProcess();
   static bool CreateForGPUProcess(Endpoint<PVRManagerParent>&& aEndpoint);
   static bool CreateForContent(Endpoint<PVRManagerParent>&& aEndpoint);
 
   bool IsSameProcess() const;
   bool HaveEventListener();
   bool HaveControllerListener();
   bool GetVRActiveStatus();
-  bool SendGamepadUpdate(const GamepadChangeEvent& aGamepadEvent);
   bool SendReplyGamepadVibrateHaptic(const uint32_t& aPromiseID);
 
  protected:
   ~VRManagerParent();
 
   PVRLayerParent* AllocPVRLayerParent(const uint32_t& aDisplayID,
                                       const uint32_t& aGroup);
   bool DeallocPVRLayerParent(PVRLayerParent* actor);
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
   void OnChannelConnected(int32_t pid) override;
 
   mozilla::ipc::IPCResult RecvRefreshDisplays();
-  mozilla::ipc::IPCResult RecvResetSensor(const uint32_t& aDisplayID);
   mozilla::ipc::IPCResult RecvSetGroupMask(const uint32_t& aDisplayID,
                                            const uint32_t& aGroupMask);
   mozilla::ipc::IPCResult RecvSetHaveEventListener(
       const bool& aHaveEventListener);
   mozilla::ipc::IPCResult RecvControllerListenerAdded();
   mozilla::ipc::IPCResult RecvControllerListenerRemoved();
   mozilla::ipc::IPCResult RecvVibrateHaptic(const uint32_t& aControllerIdx,
                                             const uint32_t& aHapticIndex,
                                             const double& aIntensity,
                                             const double& aDuration,
                                             const uint32_t& aPromiseID);
   mozilla::ipc::IPCResult RecvStopVibrateHaptic(const uint32_t& aControllerIdx);
-  mozilla::ipc::IPCResult RecvCreateVRTestSystem();
-  mozilla::ipc::IPCResult RecvCreateVRServiceTestDisplay(
-      const nsCString& aID, const uint32_t& aPromiseID);
-  mozilla::ipc::IPCResult RecvCreateVRServiceTestController(
-      const nsCString& aID, const uint32_t& aPromiseID);
-  mozilla::ipc::IPCResult RecvSetDisplayInfoToMockDisplay(
-      const uint32_t& aDeviceID, const VRDisplayInfo& aDisplayInfo);
-  mozilla::ipc::IPCResult RecvSetSensorStateToMockDisplay(
-      const uint32_t& aDeviceID, const VRHMDSensorState& aSensorState);
-  mozilla::ipc::IPCResult RecvNewButtonEventToMockController(
-      const uint32_t& aDeviceID, const long& aButton, const bool& aPressed);
-  mozilla::ipc::IPCResult RecvNewAxisMoveEventToMockController(
-      const uint32_t& aDeviceID, const long& aAxis, const double& aValue);
-  mozilla::ipc::IPCResult RecvNewPoseMoveToMockController(
-      const uint32_t& aDeviceID, const GamepadPoseState& pose);
   mozilla::ipc::IPCResult RecvStartVRNavigation(const uint32_t& aDeviceID);
   mozilla::ipc::IPCResult RecvStopVRNavigation(const uint32_t& aDeviceID,
                                                const TimeDuration& aTimeout);
   mozilla::ipc::IPCResult RecvStartActivity();
   mozilla::ipc::IPCResult RecvStopActivity();
 
+  mozilla::ipc::IPCResult RecvRunPuppet(
+      const InfallibleTArray<uint64_t>& aBuffer);
+  mozilla::ipc::IPCResult RecvResetPuppet();
+
  private:
   void RegisterWithManager();
   void UnregisterFromManager();
 
   void Bind(Endpoint<PVRManagerParent>&& aEndpoint);
 
   static void RegisterVRManagerInCompositorThread(VRManagerParent* aVRManager);
 
   void DeferredDestroy();
-  already_AddRefed<impl::VRControllerPuppet> GetControllerPuppet(
-      uint32_t aDeviceID);
 
   // This keeps us alive until ActorDestroy(), at which point we do a
   // deferred destruction of ourselves.
   RefPtr<VRManagerParent> mSelfRef;
   // Keep the compositor thread alive, until we have destroyed ourselves.
   RefPtr<CompositorThreadHolder> mCompositorThreadHolder;
 
   // Keep the VRManager alive, until we have destroyed ourselves.
   RefPtr<VRManager> mVRManagerHolder;
-  nsRefPtrHashtable<nsUint32HashKey, impl::VRControllerPuppet>
-      mVRControllerTests;
-  uint32_t mControllerTestID;
   bool mHaveEventListener;
   bool mHaveControllerListener;
   bool mIsContentChild;
 
   // When VR tabs are switched the background, we won't need to
   // initialize its session in VRService thread.
   bool mVRActiveStatus;
 };
--- a/gfx/vr/ipc/VRProcessParent.cpp
+++ b/gfx/vr/ipc/VRProcessParent.cpp
@@ -13,17 +13,16 @@
 #include "mozilla/ipc/ProtocolUtils.h"  // for IToplevelProtocol
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/TimeStamp.h"  // for TimeStamp
 #include "mozilla/Unused.h"
 #include "ProcessUtils.h"
 #include "VRChild.h"
 #include "VRManager.h"
 #include "VRThread.h"
-#include "gfxVRPuppet.h"
 
 #include "nsAppRunner.h"  // for IToplevelProtocol
 #include "mozilla/ipc/ProtocolUtils.h"
 
 using std::string;
 using std::vector;
 
 using namespace mozilla::ipc;
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -2,33 +2,32 @@
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS += [
     'external_api/moz_external_vr.h',
     'gfxVR.h',
-    'gfxVRExternal.h',
     'ipc/VRChild.h',
     'ipc/VRGPUChild.h',
     'ipc/VRGPUParent.h',
     'ipc/VRLayerChild.h',
     'ipc/VRManagerChild.h',
     'ipc/VRManagerParent.h',
     'ipc/VRMessageUtils.h',
     'ipc/VRParent.h',
     'ipc/VRProcessChild.h',
     'ipc/VRProcessManager.h',
     'ipc/VRProcessParent.h',
     'service/VRService.h',
     'VRDisplayClient.h',
-    'VRDisplayHost.h',
     'VRDisplayPresentation.h',
     'VRManager.h',
+    'VRPuppetCommandBuffer.h',
     'VRThread.h',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/gfx/layers/d3d11',
     '/gfx/thebes',
 ]
@@ -43,36 +42,33 @@ UNIFIED_SOURCES += [
     'ipc/VRManagerChild.cpp',
     'ipc/VRManagerParent.cpp',
     'ipc/VRParent.cpp',
     'ipc/VRProcessChild.cpp',
     'ipc/VRProcessManager.cpp',
     'ipc/VRProcessParent.cpp',
     'VRDisplayClient.cpp',
     'VRDisplayPresentation.cpp',
-    'VRManager.cpp',
     'VRThread.cpp',
 ]
 
-# VRDisplayHost includes MacIOSurface.h which includes Mac headers
-# which define Size and Points types in the root namespace that
-# often conflict with our own types.
 SOURCES += [
-    'gfxVRExternal.cpp',
-    'gfxVRPuppet.cpp',
-    'VRDisplayHost.cpp',
-    'VRDisplayLocal.cpp',
+    'VRManager.cpp',
+    'VRPuppetCommandBuffer.cpp',
 ]
 
 if CONFIG['OS_TARGET'] == 'Android':
     LOCAL_INCLUDES += ['/widget/android']
 else:
     DIRS += [
         'service',
     ]
+    UNIFIED_SOURCES += [
+        'VRServiceHost.cpp',
+    ]
 
 # Only target x64 for vrhost since WebVR is only supported on 64bit.
 # Also, only use MSVC compiler for Windows-specific linking
 if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['HAVE_64BIT_BUILD'] and CONFIG['CC_TYPE'] not in ('clang', 'gcc'):
     DIRS += [
         'vrhost'
     ]
 
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/PuppetSession.cpp
@@ -0,0 +1,114 @@
+/* -*- 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 "PuppetSession.h"
+
+#include "nsString.h"
+#include "VRPuppetCommandBuffer.h"
+#include "mozilla/StaticPrefs.h"
+
+#if defined(XP_WIN)
+#  include <d3d11.h>
+#  include "mozilla/gfx/DeviceManagerDx.h"
+#elif defined(XP_MACOSX)
+#  include "mozilla/gfx/MacIOSurface.h"
+#endif
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace gfx {
+
+PuppetSession::PuppetSession() : VRSession() {}
+
+PuppetSession::~PuppetSession() { Shutdown(); }
+
+bool PuppetSession::Initialize(mozilla::gfx::VRSystemState& aSystemState) {
+  if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_puppet_enabled()) {
+    return false;
+  }
+  VRPuppetCommandBuffer::Get().Run(aSystemState);
+  if (!aSystemState.displayState.isConnected) {
+    return false;
+  }
+#if defined(XP_WIN)
+  if (!CreateD3DObjects()) {
+    Shutdown();
+    return false;
+  }
+#endif
+
+  // Succeeded
+  return true;
+}
+
+#if defined(XP_WIN)
+bool PuppetSession::CreateD3DObjects() {
+  RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
+  if (!device) {
+    return false;
+  }
+  if (!CreateD3DContext(device)) {
+    return false;
+  }
+  return true;
+}
+#endif
+
+void PuppetSession::Shutdown() {}
+
+void PuppetSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) {
+  VRPuppetCommandBuffer::Get().Run(aSystemState);
+}
+
+void PuppetSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {
+  VRPuppetCommandBuffer& puppet = VRPuppetCommandBuffer::Get();
+  puppet.Run(aSystemState);
+  if (!aSystemState.displayState.isConnected) {
+    mShouldQuit = true;
+  }
+}
+
+#if defined(XP_WIN)
+bool PuppetSession::SubmitFrame(
+    const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
+    ID3D11Texture2D* aTexture) {
+  return VRPuppetCommandBuffer::Get().SubmitFrame();
+}
+#elif defined(XP_MACOSX)
+bool PuppetSession::SubmitFrame(
+    const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
+    const VRLayerTextureHandle& aTexture) {
+  return VRPuppetCommandBuffer::Get().SubmitFrame();
+}
+#endif
+
+void PuppetSession::StopPresentation() {
+  VRPuppetCommandBuffer::Get().StopPresentation();
+}
+
+bool PuppetSession::StartPresentation() {
+  VRPuppetCommandBuffer::Get().StartPresentation();
+  return true;
+}
+
+void PuppetSession::VibrateHaptic(uint32_t aControllerIdx,
+                                  uint32_t aHapticIndex, float aIntensity,
+                                  float aDuration) {
+  VRPuppetCommandBuffer::Get().VibrateHaptic(aControllerIdx, aHapticIndex,
+                                             aIntensity, aDuration);
+}
+
+void PuppetSession::StopVibrateHaptic(uint32_t aControllerIdx) {
+  VRPuppetCommandBuffer::Get().StopVibrateHaptic(aControllerIdx);
+}
+
+void PuppetSession::StopAllHaptics() {
+  VRPuppetCommandBuffer::Get().StopAllHaptics();
+}
+
+}  // namespace gfx
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/vr/service/PuppetSession.h
@@ -0,0 +1,57 @@
+/* -*- 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 GFX_VR_SERVICE_PUPPETSESSION_H
+#define GFX_VR_SERVICE_PUPPETSESSION_H
+
+#include "VRSession.h"
+
+#include "mozilla/TimeStamp.h"
+#include "moz_external_vr.h"
+
+#if defined(XP_WIN)
+#  include <d3d11_1.h>
+#endif
+class nsITimer;
+
+namespace mozilla {
+namespace gfx {
+
+class PuppetSession : public VRSession {
+ public:
+  PuppetSession();
+  virtual ~PuppetSession();
+
+  bool Initialize(mozilla::gfx::VRSystemState& aSystemState) override;
+  void Shutdown() override;
+  void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override;
+  void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override;
+  bool StartPresentation() override;
+  void StopPresentation() override;
+  void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
+                     float aIntensity, float aDuration) override;
+  void StopVibrateHaptic(uint32_t aControllerIdx) override;
+  void StopAllHaptics() override;
+
+ protected:
+#if defined(XP_WIN)
+  bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
+                   ID3D11Texture2D* aTexture) override;
+#elif defined(XP_MACOSX)
+  bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer,
+                   const VRLayerTextureHandle& aTexture) override;
+#endif
+
+ private:
+#if defined(XP_WIN)
+  bool CreateD3DObjects();
+#endif
+};
+
+}  // namespace gfx
+}  // namespace mozilla
+
+#endif  // GFX_VR_SERVICE_PUPPETSESSION_H
--- a/gfx/vr/service/VRService.cpp
+++ b/gfx/vr/service/VRService.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VRService.h"
 #include "mozilla/StaticPrefs.h"
 #include "../gfxVRMutex.h"
 #include "base/thread.h"  // for Thread
 #include <cstring>        // for memcmp
 
+#include "PuppetSession.h"
+
 #if defined(XP_WIN)
 #  include "OculusSession.h"
 #endif
 
 #if defined(XP_WIN) || defined(XP_MACOSX) || \
     (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
 #  include "OpenVRSession.h"
 #endif
@@ -44,58 +46,46 @@ bool IsImmersiveContentActive(const mozi
     }
   }
   return false;
 }
 
 }  // anonymous namespace
 
 /*static*/
-already_AddRefed<VRService> VRService::Create() {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (!StaticPrefs::dom_vr_service_enabled()) {
-    return nullptr;
-  }
-
-  RefPtr<VRService> service = new VRService();
+already_AddRefed<VRService> VRService::Create(
+    volatile VRExternalShmem* aShmem) {
+  RefPtr<VRService> service = new VRService(aShmem);
   return service.forget();
 }
 
-VRService::VRService()
+VRService::VRService(volatile VRExternalShmem* aShmem)
     : mSystemState{},
       mBrowserState{},
       mBrowserGeneration(0),
       mServiceThread(nullptr),
       mShutdownRequested(false),
-      mAPIShmem(nullptr),
+      mAPIShmem(aShmem),
       mTargetShmemFile(0),
       mLastHapticState{},
       mFrameStartTime{},
 #if defined(XP_WIN)
       mMutex(NULL),
 #endif
-      mVRProcessEnabled(StaticPrefs::dom_vr_process_enabled()) {
+      mVRProcessEnabled(aShmem == nullptr) {
   // When we have the VR process, we map the memory
-  // of mAPIShmem from GPU process.
+  // of mAPIShmem from GPU process and pass it to the CTOR.
   // If we don't have the VR process, we will instantiate
   // mAPIShmem in VRService.
-  if (!mVRProcessEnabled) {
-    mAPIShmem = new VRExternalShmem();
-    memset(mAPIShmem, 0, sizeof(VRExternalShmem));
-  }
 }
 
 VRService::~VRService() {
+  // PSA: We must store the value of any staticPrefs preferences as this
+  // destructor will be called after staticPrefs has been shut down.
   Stop();
-
-  if (!mVRProcessEnabled && mAPIShmem) {
-    delete mAPIShmem;
-    mAPIShmem = nullptr;
-  }
 }
 
 void VRService::Refresh() {
   if (!mAPIShmem) {
     return;
   }
 
   if (mAPIShmem->state.displayState.shutdown) {
@@ -236,47 +226,59 @@ void VRService::ServiceInitialize() {
   }
 
   mShutdownRequested = false;
   memset(&mBrowserState, 0, sizeof(mBrowserState));
 
   // Try to start a VRSession
   UniquePtr<VRSession> session;
 
-  // We try Oculus first to ensure we use Oculus
-  // devices trough the most native interface
-  // when possible.
+  if (StaticPrefs::dom_vr_puppet_enabled()) {
+    // When the VR Puppet is enabled, we don't want
+    // to enumerate any real devices
+    session = MakeUnique<PuppetSession>();
+    if (!session->Initialize(mSystemState)) {
+      session = nullptr;
+    }
+  } else {
+    // We try Oculus first to ensure we use Oculus
+    // devices trough the most native interface
+    // when possible.
 #if defined(XP_WIN)
-  // Try Oculus
-  session = MakeUnique<OculusSession>();
-  if (!session->Initialize(mSystemState)) {
-    session = nullptr;
-  }
+    // Try Oculus
+    if (!session) {
+      session = MakeUnique<OculusSession>();
+      if (!session->Initialize(mSystemState)) {
+        session = nullptr;
+      }
+    }
 #endif
 
 #if defined(XP_WIN) || defined(XP_MACOSX) || \
     (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID))
-  // Try OpenVR
-  if (!session) {
-    session = MakeUnique<OpenVRSession>();
-    if (!session->Initialize(mSystemState)) {
-      session = nullptr;
+    // Try OpenVR
+    if (!session) {
+      session = MakeUnique<OpenVRSession>();
+      if (!session->Initialize(mSystemState)) {
+        session = nullptr;
+      }
     }
-  }
 #endif
 #if !defined(MOZ_WIDGET_ANDROID)
-  // Try OSVR
-  if (!session) {
-    session = MakeUnique<OSVRSession>();
-    if (!session->Initialize(mSystemState)) {
-      session = nullptr;
+    // Try OSVR
+    if (!session) {
+      session = MakeUnique<OSVRSession>();
+      if (!session->Initialize(mSystemState)) {
+        session = nullptr;
+      }
     }
-  }
 #endif
 
+  }  // if (staticPrefs:VRPuppetEnabled())
+
   if (session) {
     mSession = std::move(session);
     // Setting enumerationCompleted to true indicates to the browser
     // that it should resolve any promises in the WebVR/WebXR API
     // waiting for hardware detection.
     mSystemState.enumerationCompleted = true;
     PushState(mSystemState);
 
@@ -451,16 +453,20 @@ void VRService::PushState(const mozilla:
   // Copying the VR service state to the shmem is atomic, infallable,
   // and non-blocking on x86/x64 architectures.  Arm requires a mutex
   // that is locked for the duration of the memcpy to and from shmem on
   // both sides.
 
 #if defined(MOZ_WIDGET_ANDROID)
   if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) ==
       0) {
+    // We are casting away the volatile keyword, which is not accepted by
+    // memcpy.  It is possible (although very unlikely) that the compiler
+    // may optimize out the memcpy here as memcpy isn't explicitly safe for
+    // volatile memory in the C++ standard.
     memcpy((void*)&mAPIShmem->state, &aState, sizeof(VRSystemState));
     pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
   }
 #else
   bool state = true;
 #  if defined(XP_WIN)
   if (!XRE_IsParentProcess()) {
     WaitForMutex lock(mMutex);
@@ -500,20 +506,22 @@ void VRService::PullState(mozilla::gfx::
   if (!XRE_IsParentProcess()) {
     WaitForMutex lock(mMutex);
     status = lock.GetStatus();
   }
 #  endif  // defined(XP_WIN)
   if (status) {
     VRExternalShmem tmp;
     if (mAPIShmem->geckoGenerationA != mBrowserGeneration) {
-      memcpy(&tmp, mAPIShmem, sizeof(VRExternalShmem));
+      // TODO - (void *) cast removes volatile semantics.
+      // The memcpy is not likely to be optimized out, but is theoretically
+      // possible.  Suggest refactoring to either explicitly enforce memory
+      // order or to use locks.
+      memcpy(&tmp, (void*)mAPIShmem, sizeof(VRExternalShmem));
       if (tmp.geckoGenerationA == tmp.geckoGenerationB &&
-          tmp.geckoGenerationA != 0 && tmp.geckoGenerationA != -1) {
+          tmp.geckoGenerationA != 0) {
         memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState));
         mBrowserGeneration = tmp.geckoGenerationA;
       }
     }
   }
 #endif    // defined(MOZ_WIDGET_ANDROID)
 }
-
-VRExternalShmem* VRService::GetAPIShmem() { return mAPIShmem; }
--- a/gfx/vr/service/VRService.h
+++ b/gfx/vr/service/VRService.h
@@ -19,25 +19,25 @@ namespace gfx {
 
 class VRSession;
 
 static const int kVRFrameTimingHistoryDepth = 100;
 
 class VRService {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRService)
-  static already_AddRefed<VRService> Create();
+  static already_AddRefed<VRService> Create(
+      volatile VRExternalShmem* aShmem = nullptr);
 
   void Refresh();
   void Start();
   void Stop();
-  VRExternalShmem* GetAPIShmem();
 
  private:
-  VRService();
+  explicit VRService(volatile VRExternalShmem* aShmem);
   ~VRService();
 
   bool InitShmem();
   void PushState(const mozilla::gfx::VRSystemState& aState);
   void PullState(mozilla::gfx::VRBrowserState& aState);
 
   /**
    * VRSystemState contains the most recent state of the VR
@@ -54,25 +54,23 @@ class VRService {
    */
   VRBrowserState mBrowserState;
   int64_t mBrowserGeneration;
 
   UniquePtr<VRSession> mSession;
   base::Thread* mServiceThread;
   bool mShutdownRequested;
 
-  VRExternalShmem* MOZ_OWNING_REF mAPIShmem;
+  volatile VRExternalShmem* MOZ_OWNING_REF mAPIShmem;
   base::ProcessHandle mTargetShmemFile;
   VRHapticState mLastHapticState[kVRHapticsMaxCount];
   TimeStamp mFrameStartTime[kVRFrameTimingHistoryDepth];
 #if defined(XP_WIN)
   HANDLE mMutex;
 #endif
-  // We store the value of StaticPrefs::dom_vr_process_enabled() in
-  // mVRProcessEnabled.
   bool mVRProcessEnabled;
 
   bool IsInServiceThread();
   void UpdateHaptics();
 
   /**
    * The VR Service thread is a state machine that always has one
    * task queued depending on the state.
deleted file mode 100644
--- a/gfx/vr/service/VRServiceManager.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/* -*- 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 "VRServiceManager.h"
-#include "VRGPUChild.h"
-#include "mozilla/gfx/GPUParent.h"
-#include "mozilla/StaticPrefs.h"
-
-namespace mozilla {
-namespace gfx {
-
-VRServiceManager::VRServiceManager()
-#if !defined(MOZ_WIDGET_ANDROID)
-    : mVRService(nullptr)
-#endif
-{
-}
-
-VRServiceManager& VRServiceManager::Get() {
-  static VRServiceManager instance;
-  return instance;
-}
-
-void VRServiceManager::CreateVRProcess() {
-  // Using PGPU channel to tell the main process
-  // to create VR process.
-  RefPtr<Runnable> task =
-      NS_NewRunnableFunction("GPUParent::SendCreateVRProcess", []() -> void {
-        gfx::GPUParent* gpu = GPUParent::GetSingleton();
-        MOZ_ASSERT(gpu);
-        Unused << gpu->SendCreateVRProcess();
-      });
-
-  NS_DispatchToMainThread(task.forget());
-}
-
-void VRServiceManager::ShutdownVRProcess() {
-  if (VRGPUChild::IsCreated()) {
-    VRGPUChild* vrGPUChild = VRGPUChild::Get();
-    vrGPUChild->SendStopVRService();
-    if (!vrGPUChild->IsClosed()) {
-      vrGPUChild->Close();
-    }
-    VRGPUChild::Shutdown();
-  }
-  if (StaticPrefs::dom_vr_process_enabled()) {
-    // Using PGPU channel to tell the main process
-    // to shutdown VR process.
-    gfx::GPUParent* gpu = GPUParent::GetSingleton();
-    MOZ_ASSERT(gpu);
-    Unused << gpu->SendShutdownVRProcess();
-  }
-}
-
-void VRServiceManager::CreateService() {
-  if (!StaticPrefs::dom_vr_process_enabled()) {
-    mVRService = VRService::Create();
-  }
-}
-
-void VRServiceManager::Start() {
-  if (mVRService) {
-    mVRService->Start();
-  }
-}
-
-void VRServiceManager::Stop() {
-  if (mVRService) {
-    mVRService->Stop();
-  }
-}
-
-void VRServiceManager::Shutdown() {
-  Stop();
-  mVRService = nullptr;
-}
-
-void VRServiceManager::Refresh() {
-  if (mVRService) {
-    mVRService->Refresh();
-  }
-}
-
-bool VRServiceManager::IsServiceValid() { return (mVRService != nullptr); }
-
-VRExternalShmem* VRServiceManager::GetAPIShmem() {
-#if !defined(MOZ_WIDGET_ANDROID)
-  return mVRService->GetAPIShmem();
-#endif
-  return nullptr;
-}
-
-}  // namespace gfx
-}  // namespace mozilla
deleted file mode 100644
--- a/gfx/vr/service/VRServiceManager.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- 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 GFX_VR_SERVICE_MANAGER_H
-#define GFX_VR_SERVICE_MANAGER_H
-
-namespace mozilla {
-namespace gfx {
-