Bug 1407423 - Ensure that any time we have loaded the Oculus runtime libary, we are polling ShouldQuit,r=daoshengmu
authorKearwood "Kip" Gilbert <kgilbert@mozilla.com>
Tue, 10 Oct 2017 14:42:37 -0700
changeset 395200 4cee79cb6c236c232bc143056f0bc47358123672
parent 395199 8f233446cd06b7c91f66327cb8c8b0866eb68919
child 395201 b8b24db533eb4d1ca0f5a24eb4cd0bd9e425779e
push id33034
push userrgurzau@mozilla.com
push dateWed, 06 Dec 2017 09:54:34 +0000
treeherdermozilla-central@79d3e25106f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdaoshengmu
bugs1407423
milestone59.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 1407423 - Ensure that any time we have loaded the Oculus runtime libary, we are polling ShouldQuit,r=daoshengmu - Ensure ovr_GetSessionStatus is polled even when a VR presentation is not active. - When we fail to initialize an Oculus Session or detect VR hardware, immediately unload the Oculus Library as we can't poll for ShouldQuit without a valid Oculus session. - When we poll ovr_GetSessionStatus, we are now updating the mounted state in VRDisplayInfo::mIsMounted. - Added prefs to control enumeration throttling and timeout to release VR hardware when inactive. - Some refactoring to make frame loop more understandable and less brittle. - When throttling enumeration, we ensure that all other VR apis also throttle enumeration so that they don't pick up the same device during throttling. - Some long functions in VRManager have been broken up and had their inner-workings documented in more detail. MozReview-Commit-ID: CEYwwQ9mYd0
gfx/thebes/gfxPrefs.h
gfx/vr/VRDisplayHost.h
gfx/vr/VRManager.cpp
gfx/vr/VRManager.h
gfx/vr/gfxVR.cpp
gfx/vr/gfxVR.h
gfx/vr/gfxVROSVR.cpp
gfx/vr/gfxVROSVR.h
gfx/vr/gfxVROculus.cpp
gfx/vr/gfxVROculus.h
gfx/vr/gfxVROpenVR.cpp
gfx/vr/gfxVROpenVR.h
gfx/vr/gfxVRPuppet.cpp
gfx/vr/gfxVRPuppet.h
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -362,16 +362,19 @@ private:
   DECL_GFX_PREF(Live, "dom.vr.autoactivate.enabled",           VRAutoActivateEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.controller_trigger_threshold",   VRControllerTriggerThreshold, float, 0.1f);
   DECL_GFX_PREF(Live, "dom.vr.navigation.timeout",             VRNavigationTimeout, int32_t, 1000);
   DECL_GFX_PREF(Once, "dom.vr.oculus.enabled",                 VROculusEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.oculus.present.timeout",         VROculusPresentTimeout, int32_t, 10000);
   DECL_GFX_PREF(Live, "dom.vr.oculus.quit.timeout",            VROculusQuitTimeout, int32_t, 30000);
   DECL_GFX_PREF(Once, "dom.vr.openvr.enabled",                 VROpenVREnabled, bool, false);
   DECL_GFX_PREF(Once, "dom.vr.osvr.enabled",                   VROSVREnabled, bool, false);
+  DECL_GFX_PREF(Live, "dom.vr.controller.enumerate.interval",  VRControllerEnumerateInterval, int32_t, 1000);
+  DECL_GFX_PREF(Live, "dom.vr.display.enumerate.interval",     VRDisplayEnumerateInterval, int32_t, 5000);
+  DECL_GFX_PREF(Live, "dom.vr.inactive.timeout",               VRInactiveTimeout, int32_t, 5000);
   DECL_GFX_PREF(Live, "dom.vr.poseprediction.enabled",         VRPosePredictionEnabled, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.require-gesture",                VRRequireGesture, bool, true);
   DECL_GFX_PREF(Live, "dom.vr.puppet.enabled",                 VRPuppetEnabled, bool, false);
   DECL_GFX_PREF(Live, "dom.vr.puppet.submitframe",             VRPuppetSubmitFrame, uint32_t, 0);
   DECL_GFX_PREF(Live, "dom.vr.display.rafMaxDuration",         VRDisplayRafMaxDuration, uint32_t, 50);
   DECL_GFX_PREF(Live, "dom.w3c_pointer_events.enabled",        PointerEventsEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "general.smoothScroll",                  SmoothScrollEnabled, bool, true);
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -36,17 +36,17 @@ public:
   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 NotifyVSync();
+  void NotifyVSync();
 
   void StartFrame();
   void SubmitFrame(VRLayerParent* aLayer,
                    const layers::SurfaceDescriptor& aTexture,
                    uint64_t aFrameId,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
 
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -51,16 +51,18 @@ VRManager::ManagerInit()
     sVRManagerSingleton = new VRManager();
     ClearOnShutdown(&sVRManagerSingleton);
   }
 }
 
 VRManager::VRManager()
   : mInitialized(false)
   , mVRTestSystemCreated(false)
+  , mVRDisplaysRequested(false)
+  , mVRControllersRequested(false)
 {
   MOZ_COUNT_CTOR(VRManager);
   MOZ_ASSERT(sVRManagerSingleton == nullptr);
 
   RefPtr<VRSystemManager> mgr;
 
   /**
    * We must add the VRDisplayManager's to mManagers in a careful order to
@@ -169,86 +171,76 @@ VRManager::RemoveVRManagerParent(VRManag
 {
   mVRManagerParents.RemoveEntry(aVRManagerParent);
   if (mVRManagerParents.IsEmpty()) {
     Destroy();
   }
 }
 
 void
-VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp)
+VRManager::UpdateRequestedDevices()
 {
-  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
-  const double kVRDisplayRefreshMaxDuration = 5000; // milliseconds
-  const double kVRDisplayInactiveMaxDuration = 30000; // milliseconds
-
   bool bHaveEventListener = false;
   bool bHaveControllerListener = false;
 
   for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
     VRManagerParent *vmp = iter.Get()->GetKey();
     bHaveEventListener |= vmp->HaveEventListener();
     bHaveControllerListener |= vmp->HaveControllerListener();
   }
 
-  // VRDisplayHost::NotifyVSync may modify mVRDisplays, so we iterate
-  // through a local copy here.
-  nsTArray<RefPtr<VRDisplayHost>> displays;
-  for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
-    displays.AppendElement(iter.UserData());
-  }
-  for (const auto& display: displays) {
-    display->NotifyVSync();
+  mVRDisplaysRequested = bHaveEventListener;
+  // We only currently allow controllers to be used when
+  // also activating a VR display
+  mVRControllersRequested = mVRDisplaysRequested && bHaveControllerListener;
+}
+
+/**
+ * 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)
+{
+  MOZ_ASSERT(VRListenerThreadHolder::IsInVRListenerThread());
+  UpdateRequestedDevices();
+
+  for (const auto& manager : mManagers) {
+    manager->NotifyVSync();
   }
 
-  if (bHaveEventListener) {
-    // If content has set an EventHandler to be notified of VR display events
-    // 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 (mLastRefreshTime.IsNull()) {
-      // This is the first vsync, must refresh VR displays
-      RefreshVRDisplays();
-      if (bHaveControllerListener) {
-        RefreshVRControllers();
-      }
-      mLastRefreshTime = TimeStamp::Now();
-    } else {
-      // We don't have to do this every frame, so check if we
-      // have refreshed recently.
-      TimeDuration duration = TimeStamp::Now() - mLastRefreshTime;
-      if (duration.ToMilliseconds() > kVRDisplayRefreshMaxDuration) {
-        RefreshVRDisplays();
-        if (bHaveControllerListener) {
-          RefreshVRControllers();
-        }
-        mLastRefreshTime = TimeStamp::Now();
-      }
-    }
+  // 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.
+  RefreshVRDisplays();
 
-    if (bHaveControllerListener) {
-      for (const auto& manager: mManagers) {
-        if (!manager->GetIsPresenting()) {
-          manager->HandleInput();
-        }
-      }
-    }
-  }
+  // Update state and enumeration of VR controllers
+  RefreshVRControllers();
+
+  CheckForInactiveTimeout();
+}
 
+void
+VRManager::CheckForInactiveTimeout()
+{
   // Shut down the VR devices when not in use
-  if (bHaveEventListener || bHaveControllerListener) {
+  if (mVRDisplaysRequested || mVRControllersRequested) {
     // We are using a VR device, keep it alive
     mLastActiveTime = TimeStamp::Now();
-  } else if (mLastActiveTime.IsNull()) {
+  }
+  else if (mLastActiveTime.IsNull()) {
     Shutdown();
-  } else {
+  }
+  else {
     TimeDuration duration = TimeStamp::Now() - mLastActiveTime;
-    if (duration.ToMilliseconds() > kVRDisplayInactiveMaxDuration) {
+    if (duration.ToMilliseconds() > gfxPrefs::VRInactiveTimeout()) {
       Shutdown();
     }
   }
 }
 
 void
 VRManager::NotifyVRVsync(const uint32_t& aDisplayID)
 {
@@ -263,35 +255,89 @@ VRManager::NotifyVRVsync(const uint32_t&
   if (display) {
     display->StartFrame();
   }
 
   RefreshVRDisplays();
 }
 
 void
+VRManager::EnumerateVRDisplays()
+{
+  /**
+   * Throttle the rate of enumeration to the interval set in
+   * VRDisplayEnumerateInterval
+   */
+  if (!mLastDisplayEnumerationTime.IsNull()) {
+    TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime;
+    if (duration.ToMilliseconds() < gfxPrefs::VRDisplayEnumerateInterval()) {
+      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 we get this far, don't try again until
+   * the VRDisplayEnumerateInterval elapses
+   */
+  mLastDisplayEnumerationTime = TimeStamp::Now();
+
+  /**
+   * 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()) {
+      return;
+    }
+  }
+}
+
+void
 VRManager::RefreshVRDisplays(bool aMustDispatch)
 {
-  nsTArray<RefPtr<gfx::VRDisplayHost> > displays;
+  /**
+  * 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();
+  }
 
-  /** We don't wish to enumerate the same display from multiple managers,
-   * so stop as soon as we get a display.
-   * It is still possible to get multiple displays from a single manager,
-   * but do not wish to mix-and-match for risk of reporting a duplicate.
-   *
-   * XXX - Perhaps there will be a better way to detect duplicate displays
-   *       in the future.
+  /**
+   * 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.
    */
-  for (uint32_t i = 0; i < mManagers.Length() && displays.Length() == 0; ++i) {
-    if (mManagers[i]->GetHMDs(displays)) {
-      // GetHMDs returns true to indicate that no further enumeration from
-      // other managers should be performed.  This prevents erraneous
-      // redundant enumeration of the same HMD by multiple managers.
-      break;
-    }
+  nsTArray<RefPtr<gfx::VRDisplayHost> > displays;
+  for (const auto& manager: mManagers) {
+    manager->GetHMDs(displays);
   }
 
   bool displayInfoChanged = false;
   bool displaySetChanged = false;
 
   if (displays.Length() != mVRDisplays.Count()) {
     // Catch cases where a VR display has been removed
     displaySetChanged = true;
@@ -378,19 +424,19 @@ VRManager::GetVRControllerInfo(nsTArray<
     gfx::VRControllerHost* controller = iter.UserData();
     aControllerInfo.AppendElement(VRControllerInfo(controller->GetControllerInfo()));
   }
 }
 
 void
 VRManager::RefreshVRControllers()
 {
-  nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
+  ScanForControllers();
 
-  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;
 
@@ -414,19 +460,35 @@ VRManager::RefreshVRControllers()
                          controller);
     }
   }
 }
 
 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() < gfxPrefs::VRControllerEnumerateInterval()) {
+      return;
+    }
+  }
+
+  // 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();
   }
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -59,32 +59,38 @@ protected:
 
 private:
 
   void Init();
   void Destroy();
   void Shutdown();
 
   void DispatchVRDisplayInfoUpdate();
+  void UpdateRequestedDevices();
+  void EnumerateVRDisplays();
+  void CheckForInactiveTimeout();
 
   typedef nsTHashtable<nsRefPtrHashKey<VRManagerParent>> VRManagerParentSet;
   VRManagerParentSet mVRManagerParents;
 
   typedef nsTArray<RefPtr<VRSystemManager>> VRSystemManagerArray;
   VRSystemManagerArray mManagers;
 
   typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRDisplayHost> VRDisplayHostHashMap;
   VRDisplayHostHashMap mVRDisplays;
 
   typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRControllerHost> VRControllerHostHashMap;
   VRControllerHostHashMap mVRControllers;
 
   Atomic<bool> mInitialized;
 
-  TimeStamp mLastRefreshTime;
+  TimeStamp mLastControllerEnumerationTime;
+  TimeStamp mLastDisplayEnumerationTime;
   TimeStamp mLastActiveTime;
   bool mVRTestSystemCreated;
+  bool mVRDisplaysRequested;
+  bool mVRControllersRequested;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif // GFX_VR_MANAGER_H
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.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 <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;
 
@@ -27,16 +28,68 @@ VRSystemManager::AllocateDisplayID()
 }
 
 /* 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();
+  }
+}
+
+/**
+ * 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);
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -343,17 +343,20 @@ 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 bool GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) = 0;
+  virtual void Enumerate() = 0;
+  virtual void NotifyVSync();
+  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;
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -545,35 +545,61 @@ VRSystemManagerOSVR::Shutdown()
   if (m_ctx) {
     osvr_ClientFreeDisplay(m_display);
   }
   // osvr checks that m_ctx or m_iface are not null
   osvr_ClientFreeInterface(m_ctx, m_iface);
   osvr_ClientShutdown(m_ctx);
 }
 
-bool
-VRSystemManagerOSVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+void
+VRSystemManagerOSVR::NotifyVSync()
+{
+  VRSystemManager::NotifyVSync();
+
+  // TODO - Check for device disconnection or other OSVR events
+}
+
+void
+VRSystemManagerOSVR::Enumerate()
 {
   // make sure context, interface and display are initialized
   CheckOSVRStatus();
 
   if (!Init()) {
-    return false;
+    return;
   }
 
   mHMDInfo = new VRDisplayOSVR(&m_ctx, &m_iface, &m_display);
+}
 
+bool
+VRSystemManagerOSVR::ShouldInhibitEnumeration()
+{
+  if (VRSystemManager::ShouldInhibitEnumeration()) {
+    return true;
+  }
   if (mHMDInfo) {
-    aHMDResult.AppendElement(mHMDInfo);
+    // 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
+VRSystemManagerOSVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+{
+  if (mHMDInfo) {
+    aHMDResult.AppendElement(mHMDInfo);
+  }
+}
+
 bool
 VRSystemManagerOSVR::GetIsPresenting()
 {
   if (mHMDInfo) {
     VRDisplayInfo displayInfo(mHMDInfo->GetDisplayInfo());
     return displayInfo.GetPresentingGroups() != kVRGroupNone;
   }
 
--- a/gfx/vr/gfxVROSVR.h
+++ b/gfx/vr/gfxVROSVR.h
@@ -75,17 +75,20 @@ protected:
 } // namespace impl
 
 class VRSystemManagerOSVR : public VRSystemManager
 {
 public:
   static already_AddRefed<VRSystemManagerOSVR> Create();
   virtual void Destroy() override;
   virtual void Shutdown() override;
-  virtual bool GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
+  virtual void NotifyVSync() override;
+  virtual void Enumerate() override;
+  virtual bool ShouldInhibitEnumeration() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
   virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                              double aIntensity, double aDuration,
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -200,51 +200,75 @@ FromFovPort(const ovrFovPort& aFOV)
 
 } // namespace
 
 VROculusSession::VROculusSession()
   : mOvrLib(nullptr)
   , mSession(nullptr)
   , mInitFlags((ovrInitFlags)0)
   , mTextureSet(nullptr)
-  , mPresenting(false)
+  , mRequestPresentation(false)
+  , mRequestTracking(false)
   , mDrawBlack(false)
+  , mIsConnected(false)
+  , mIsMounted(false)
 {
 }
 
 ovrSession
 VROculusSession::Get()
 {
   MOZ_ASSERT(mSession);
   return mSession;
 }
 
 bool
 VROculusSession::IsTrackingReady() const
 {
-  return mSession != nullptr;
+  // We should return true only if the HMD is connected and we
+  // are ready for tracking
+  MOZ_ASSERT(!mIsConnected || mSession);
+  return mIsConnected;
 }
 
 bool
-VROculusSession::IsRenderReady() const
+VROculusSession::IsPresentationReady() const
 {
   return !mRenderTargets.IsEmpty();
 }
 
+bool
+VROculusSession::IsMounted() const
+{
+  return mIsMounted;
+}
+
 void
 VROculusSession::StopTracking()
 {
-  Uninitialize(true);
+  if (mRequestTracking) {
+    mRequestTracking = false;
+    Refresh();
+  }
+}
+
+void
+VROculusSession::StartTracking()
+{
+  if (!mRequestTracking) {
+    mRequestTracking = true;
+    Refresh();
+  }
 }
 
 void
 VROculusSession::StartPresentation(const IntSize& aSize)
 {
-  if (!mPresenting) {
-    mPresenting = true;
+  if (!mRequestPresentation) {
+    mRequestPresentation = true;
     mTelemetry.Clear();
     mTelemetry.mPresentationStart = TimeStamp::Now();
 
     ovrPerfStats perfStats;
     if (ovr_GetPerfStats(mSession, &perfStats) == ovrSuccess) {
       if (perfStats.FrameStatsCount) {
         mTelemetry.mLastDroppedFrameCount = perfStats.FrameStats[0].AppDroppedFrameCount;
       }
@@ -254,19 +278,19 @@ VROculusSession::StartPresentation(const
   // Update the size, even when we are already presenting.
   mPresentationSize = aSize;
   Refresh();
 }
 
 void
 VROculusSession::StopPresentation()
 {
-  if (mPresenting) {
+  if (mRequestPresentation) {
     mLastPresentationEnd = TimeStamp::Now();
-    mPresenting = false;
+    mRequestPresentation = false;
 
     const TimeDuration duration = mLastPresentationEnd - mTelemetry.mPresentationStart;
     Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, 1);
     Telemetry::Accumulate(Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS,
                           duration.ToMilliseconds());
 
     if (mTelemetry.IsLastDroppedFrameValid() && duration.ToSeconds()) {
       ovrPerfStats perfStats;
@@ -311,16 +335,18 @@ VROculusSession::StopRendering()
   mDevice = nullptr;
 }
 
 void
 VROculusSession::StopSession()
 {
   if (mSession) {
     ovr_Destroy(mSession);
+    mIsConnected = false;
+    mIsMounted = false;
     mSession = nullptr;
   }
 }
 
 void
 VROculusSession::StopLib()
 {
   if (mInitFlags) {
@@ -333,19 +359,24 @@ void
 VROculusSession::Refresh(bool aForceRefresh)
 {
   // We are waiting for drawing the black layer command for
   // Compositor thread. Ignore Refresh() calls from other threads.
   if (mDrawBlack && !aForceRefresh) {
     return;
   }
 
+  if (!mRequestTracking) {
+    Uninitialize(true);
+    return;
+  }
+
   ovrInitFlags flags = (ovrInitFlags)(ovrInit_RequestVersion | ovrInit_MixedRendering);
   bool bInvisible = true;
-  if (mPresenting) {
+  if (mRequestPresentation) {
     bInvisible = false;
   } else if (!mLastPresentationEnd.IsNull()) {
     TimeDuration duration = TimeStamp::Now() - mLastPresentationEnd;
     TimeDuration timeout = TimeDuration::FromMilliseconds(gfxPrefs::VROculusPresentTimeout());
     if (timeout > TimeDuration(0) && duration < timeout) {
       // Do not immediately re-initialize with an invisible session after
       // the end of a VR presentation.  Waiting for the configured duraction
       // ensures that the user will not drop to Oculus Home during VR link
@@ -380,25 +411,35 @@ VROculusSession::Refresh(bool aForceRefr
   if (bInvisible) {
     flags = (ovrInitFlags)(flags | ovrInit_Invisible);
   }
 
   if (mInitFlags != flags) {
     Uninitialize(false);
   }
 
-  Initialize(flags);
+  if(!Initialize(flags)) {
+    // If we fail to initialize, ensure the Oculus libraries
+    // are unloaded, as we can't poll for ovrSessionStatus::ShouldQuit
+    // without an active ovrSession.
+    Uninitialize(true);
+  }
 
   if (mSession) {
     ovrSessionStatus status;
     if (OVR_SUCCESS(ovr_GetSessionStatus(mSession, &status))) {
+      mIsConnected = status.HmdPresent;
+      mIsMounted = status.HmdMounted;
       if (status.ShouldQuit) {
         mLastShouldQuit = TimeStamp::Now();
         Uninitialize(true);
       }
+    } else {
+      mIsConnected = false;
+      mIsMounted = false;
     }
   }
 }
 
 bool
 VROculusSession::IsQuitTimeoutActive()
 {
   // If Oculus asked us to quit our session, do not try to initialize again
@@ -433,17 +474,17 @@ VROculusSession::Initialize(ovrInitFlags
     return false;
   }
   return true;
 }
 
 bool
 VROculusSession::StartRendering()
 {
-  if (!mPresenting) {
+  if (!mRequestPresentation) {
     // Nothing to do if we aren't presenting
     return true;
   }
   if (!mDevice) {
     mDevice = gfx::DeviceManagerDx::Get()->GetVRDevice();
     if (!mDevice) {
       NS_WARNING("Failed to get a D3D11Device for Oculus");
       return false;
@@ -969,17 +1010,17 @@ VRDisplayOculus::GetSensorState(double a
 
 void
 VRDisplayOculus::StartPresentation()
 {
   if (!CreateD3DObjects()) {
     return;
   }
   mSession->StartPresentation(IntSize(mDisplayInfo.mEyeResolution.width * 2, mDisplayInfo.mEyeResolution.height));
-  if (!mSession->IsRenderReady()) {
+  if (!mSession->IsPresentationReady()) {
     return;
   }
 
   if (!mQuadVS) {
     if (FAILED(mDevice->CreateVertexShader(sLayerQuadVS.mData, sLayerQuadVS.mLength, nullptr, &mQuadVS))) {
       NS_WARNING("Failed to create vertex shader for Oculus");
       return;
     }
@@ -1099,17 +1140,17 @@ VRDisplayOculus::SubmitFrame(ID3D11Textu
     return false;
   }
 
   AutoRestoreRenderState restoreState(this);
   if (!restoreState.IsSuccess()) {
     return false;
   }
 
-  if (!mSession->IsRenderReady()) {
+  if (!mSession->IsPresentationReady()) {
     return false;
   }
   /**
     * XXX - We should resolve fail the promise returned by
     *       VRDisplay.requestPresent() when the DX11 resources fail allocation
     *       in VRDisplayOculus::StartPresentation().
     *       Bailing out here prevents the crash but content should be aware
     *       that frames are not being presented.
@@ -1244,28 +1285,20 @@ VRDisplayOculus::SubmitFrame(ID3D11Textu
      */
     return false;
   }
 
   return true;
 }
 
 void
-VRDisplayOculus::NotifyVSync()
+VRDisplayOculus::Refresh()
 {
-  mSession->Refresh();
-  if (mSession->IsTrackingReady()) {
-    ovrSessionStatus sessionStatus;
-    ovrResult ovr = ovr_GetSessionStatus(mSession->Get(), &sessionStatus);
-    mDisplayInfo.mIsConnected = (ovr == ovrSuccess && sessionStatus.HmdPresent);
-  } else {
-    mDisplayInfo.mIsConnected = false;
-  }
-
-  VRDisplayHost::NotifyVSync();
+  mDisplayInfo.mIsConnected = mSession->IsTrackingReady();
+  mDisplayInfo.mIsMounted = mSession->IsMounted();
 }
 
 VRControllerOculus::VRControllerOculus(dom::GamepadHand aHand, uint32_t aDisplayID)
   : VRControllerHost(VRDeviceType::Oculus, aHand, aDisplayID)
   , mIndexTrigger(0.0f)
   , mHandTrigger(0.0f)
   , mVibrateThread(nullptr)
   , mIsVibrateStopped(false)
@@ -1516,47 +1549,76 @@ VRSystemManagerOculus::Shutdown()
   }
   RemoveControllers();
   if (mDisplay) {
     mDisplay->Destroy();
   }
   mDisplay = nullptr;
 }
 
+void
+VRSystemManagerOculus::NotifyVSync()
+{
+  VRSystemManager::NotifyVSync();
+  if (!mSession) {
+    return;
+  }
+  mSession->Refresh();
+  if (mDisplay) {
+    mDisplay->Refresh();
+  }
+  // Detect disconnection
+  if (!mSession->IsTrackingReady()) {
+    // No HMD connected
+    mDisplay = nullptr;
+  }
+}
+
 bool
-VRSystemManagerOculus::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+VRSystemManagerOculus::ShouldInhibitEnumeration()
+{
+  if (VRSystemManager::ShouldInhibitEnumeration()) {
+    return true;
+  }
+  if (mDisplay) {
+    // When we find an Oculus VR device, don't
+    // allow any further enumeration as it
+    // may get picked up redundantly by other
+    // API's such as OpenVR.
+    return true;
+  }
+  if (mSession && mSession->IsQuitTimeoutActive()) {
+    // When we are responding to ShouldQuit, we return true here
+    // to prevent further enumeration by other VRSystemManager's such as
+    // VRSystemManagerOpenVR which would also enumerate the connected Oculus
+    // HMD, resulting in interference with the Oculus runtime software updates.
+    return true;
+  }
+  return false;
+}
+
+void
+VRSystemManagerOculus::Enumerate()
 {
   if (!mSession) {
     mSession = new VROculusSession();
   }
-  mSession->Refresh();
-  if (mSession->IsQuitTimeoutActive()) {
-    // We have responded to a ShouldQuit flag set by the Oculus runtime
-    // and are waiting for a timeout duration to elapse before allowing
-    // re-initialization of the Oculus OVR lib.  We return true in this case
-    // to prevent further enumeration by other VRSystemManager's such as
-    // VRSystemManagerOpenVR which would also enumerate the connected Oculus
-    // HMD, resulting in interference with the Oculus runtime software updates.
-    mDisplay = nullptr;
-    return true;
-  }
-
-  if (!mSession->IsTrackingReady()) {
-    // No HMD connected.
-    mDisplay = nullptr;
-  } else if (mDisplay == nullptr) {
+  mSession->StartTracking();
+  if (mDisplay == nullptr && mSession->IsTrackingReady()) {
     // HMD Detected
     mDisplay = new VRDisplayOculus(mSession);
   }
+}
 
+void
+VRSystemManagerOculus::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+{
   if (mDisplay) {
     aHMDResult.AppendElement(mDisplay);
-    return true;
   }
-  return false;
 }
 
 bool
 VRSystemManagerOculus::GetIsPresenting()
 {
   if (mDisplay) {
     VRDisplayInfo displayInfo(mDisplay->GetDisplayInfo());
     return displayInfo.GetPresentingGroups() != 0;
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -37,22 +37,24 @@ enum class OculusControllerAxisType : ui
 
 class VROculusSession
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VROculusSession);
   friend class VRDisplayOculus;
 public:
   VROculusSession();
   void Refresh(bool aForceRefresh = false);
+  void StartTracking();
+  void StopTracking();
   bool IsTrackingReady() const;
-  bool IsRenderReady() const;
-  ovrSession Get();
   void StartPresentation(const IntSize& aSize);
   void StopPresentation();
-  void StopTracking();
+  bool IsPresentationReady() const;
+  bool IsMounted() const;
+  ovrSession Get();
   bool IsQuitTimeoutActive();
   already_AddRefed<layers::CompositingRenderTargetD3D11> GetNextRenderTarget();
   ovrTextureSwapChain GetSwapChain();
 
 private:
   PRLibrary* mOvrLib;
   ovrSession mSession;
   ovrInitFlags mInitFlags;
@@ -60,18 +62,22 @@ private:
   nsTArray<RefPtr<layers::CompositingRenderTargetD3D11>> mRenderTargets;
   IntSize mPresentationSize;
   RefPtr<ID3D11Device> mDevice;
   // The timestamp of the last time Oculus set ShouldQuit to true.
   TimeStamp mLastShouldQuit;
   // The timestamp of the last ending presentation
   TimeStamp mLastPresentationEnd;
   VRTelemetry mTelemetry;
-  bool mPresenting;
+  bool mRequestPresentation;
+  bool mRequestTracking;
+  bool mTracking;
   bool mDrawBlack;
+  bool mIsConnected;
+  bool mIsMounted;
 
   ~VROculusSession();
   void Uninitialize(bool aUnloadLib);
   bool Initialize(ovrInitFlags aFlags);
   bool LoadOvrLib();
   void UnloadOvrLib();
   bool StartSession();
   void StopSession();
@@ -79,32 +85,32 @@ private:
   void StopLib();
   bool StartRendering();
   void StopRendering();
 };
 
 class VRDisplayOculus : public VRDisplayHost
 {
 public:
-  virtual void NotifyVSync() override;
   void ZeroSensor() override;
 
 protected:
   virtual VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
   virtual bool SubmitFrame(ID3D11Texture2D* aSource,
                            const IntSize& aSize,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
   void UpdateStageParameters();
 
 public:
   explicit VRDisplayOculus(VROculusSession* aSession);
   void Destroy();
+  void Refresh();
 
 protected:
   virtual ~VRDisplayOculus();
 
   VRHMDSensorState GetSensorState(double absTime);
 
   ovrHmdDesc mDesc;
   RefPtr<VROculusSession> mSession;
@@ -171,17 +177,20 @@ private:
 } // namespace impl
 
 class VRSystemManagerOculus : public VRSystemManager
 {
 public:
   static already_AddRefed<VRSystemManagerOculus> Create();
   virtual void Destroy() override;
   virtual void Shutdown() override;
-  virtual bool GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
+  virtual void Enumerate() override;
+  virtual void NotifyVSync() override;
+  virtual bool ShouldInhibitEnumeration() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
   virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                              double aIntensity, double aDuration, const VRManagerPromise& aPromise) override;
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -198,18 +198,20 @@ VRDisplayOpenVR::ZeroSensor()
 
 bool
 VRDisplayOpenVR::GetIsHmdPresent()
 {
   return mIsHmdPresent;
 }
 
 void
-VRDisplayOpenVR::PollEvents()
+VRDisplayOpenVR::Refresh()
 {
+  mIsHmdPresent = ::vr::VR_IsHmdPresent();
+
   ::vr::VREvent_t event;
   while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) {
     switch (event.eventType) {
       case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
         if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
           mDisplayInfo.mIsMounted = true;
         }
         break;
@@ -240,18 +242,16 @@ VRDisplayOpenVR::PollEvents()
         break;
     }
   }
 }
 
 VRHMDSensorState
 VRDisplayOpenVR::GetSensorState()
 {
-  PollEvents();
-
   const uint32_t posesSize = ::vr::k_unTrackedDeviceIndex_Hmd + 1;
   ::vr::TrackedDevicePose_t poses[posesSize];
   // Note: We *must* call WaitGetPoses in order for any rendering to happen at all.
   mVRCompositor->WaitGetPoses(nullptr, 0, poses, posesSize);
   gfx::Matrix4x4 headToEyeTransforms[2];
   UpdateEyeParameters(headToEyeTransforms);
 
   VRHMDSensorState result{};
@@ -418,27 +418,16 @@ VRDisplayOpenVR::SubmitFrame(MacIOSurfac
                          ::vr::ETextureType::TextureType_IOSurface,
                          aSize, aLeftEyeRect, aRightEyeRect);
   }
   return result;
 }
 
 #endif
 
-void
-VRDisplayOpenVR::NotifyVSync()
-{
-  // We check if HMD is available once per frame.
-  mIsHmdPresent = ::vr::VR_IsHmdPresent();
-  // Make sure we respond to OpenVR events even when not presenting
-  PollEvents();
-
-  VRDisplayHost::NotifyVSync();
-}
-
 VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aDisplayID,
                                        uint32_t aNumButtons, uint32_t aNumTriggers,
                                        uint32_t aNumAxes, const nsCString& aId)
   : VRControllerHost(VRDeviceType::OpenVR, aHand, aDisplayID)
   , mTrigger(aNumTriggers)
   , mAxisMove(aNumAxes)
   , mVibrateThread(nullptr)
   , mIsVibrateStopped(false)
@@ -632,60 +621,97 @@ VRSystemManagerOpenVR::Shutdown()
 {
   if (mOpenVRHMD) {
     mOpenVRHMD = nullptr;
   }
   RemoveControllers();
   mVRSystem = nullptr;
 }
 
-bool
-VRSystemManagerOpenVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+void
+VRSystemManagerOpenVR::NotifyVSync()
 {
-  if (!::vr::VR_IsHmdPresent() ||
-      (mOpenVRHMD && !mOpenVRHMD->GetIsHmdPresent())) {
-    // OpenVR runtime could be quit accidentally,
-    // and we make it re-initialize.
-    mOpenVRHMD = nullptr;
-    mVRSystem = nullptr;
-  } else if (mOpenVRHMD == nullptr) {
+  VRSystemManager::NotifyVSync();
+
+  // Avoid doing anything unless we have already
+  // successfully enumerated and loaded the OpenVR
+  // runtime.
+  if (mVRSystem == nullptr) {
+    return;
+  }
+
+  if (mOpenVRHMD) {
+    mOpenVRHMD->Refresh();
+    if (!mOpenVRHMD->GetIsHmdPresent()) {
+      // OpenVR runtime could be quit accidentally
+      // or a device could be disconnected.
+      // We free up resources and must re-initialize
+      // if a device is detected again later.
+      mOpenVRHMD = nullptr;
+      mVRSystem = nullptr;
+    }
+  }
+}
+
+void
+VRSystemManagerOpenVR::Enumerate()
+{
+  if (mOpenVRHMD == nullptr && ::vr::VR_IsHmdPresent()) {
     ::vr::HmdError err;
 
     ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene);
     if (err) {
-      return false;
+      return;
     }
 
     ::vr::IVRSystem *system = (::vr::IVRSystem *)::vr::VR_GetGenericInterface(::vr::IVRSystem_Version, &err);
     if (err || !system) {
       ::vr::VR_Shutdown();
-      return false;
+      return;
     }
     ::vr::IVRChaperone *chaperone = (::vr::IVRChaperone *)::vr::VR_GetGenericInterface(::vr::IVRChaperone_Version, &err);
     if (err || !chaperone) {
       ::vr::VR_Shutdown();
-      return false;
+      return;
     }
     ::vr::IVRCompositor *compositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface(::vr::IVRCompositor_Version, &err);
     if (err || !compositor) {
       ::vr::VR_Shutdown();
-      return false;
+      return;
     }
 
     mVRSystem = system;
     mOpenVRHMD = new VRDisplayOpenVR(system, chaperone, compositor);
   }
+}
 
+bool
+VRSystemManagerOpenVR::ShouldInhibitEnumeration()
+{
+  if (VRSystemManager::ShouldInhibitEnumeration()) {
+    return true;
+  }
   if (mOpenVRHMD) {
-    aHMDResult.AppendElement(mOpenVRHMD);
+    // 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
+VRSystemManagerOpenVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+{
+  if (mOpenVRHMD) {
+    aHMDResult.AppendElement(mOpenVRHMD);
+  }
+}
+
 bool
 VRSystemManagerOpenVR::GetIsPresenting()
 {
   if (mOpenVRHMD) {
     VRDisplayInfo displayInfo(mOpenVRHMD->GetDisplayInfo());
     return displayInfo.GetPresentingGroups() != kVRGroupNone;
   }
 
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -25,17 +25,16 @@ class MacIOSurface;
 #endif
 namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayOpenVR : public VRDisplayHost
 {
 public:
-  virtual void NotifyVSync() override;
   void ZeroSensor() override;
   bool GetIsHmdPresent();
 
 protected:
   virtual VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
 #if defined(XP_WIN)
@@ -49,33 +48,32 @@ protected:
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #endif
 
 public:
   explicit VRDisplayOpenVR(::vr::IVRSystem *aVRSystem,
                            ::vr::IVRChaperone *aVRChaperone,
                            ::vr::IVRCompositor *aVRCompositor);
-
+  void Refresh();
 protected:
   virtual ~VRDisplayOpenVR();
   void Destroy();
 
   // not owned by us; global from OpenVR
   ::vr::IVRSystem *mVRSystem;
   ::vr::IVRChaperone *mVRChaperone;
   ::vr::IVRCompositor *mVRCompositor;
 
   VRTelemetry mTelemetry;
   bool mIsPresenting;
   bool mIsHmdPresent;
 
   void UpdateStageParameters();
   void UpdateEyeParameters(gfx::Matrix4x4* aHeadToEyeTransforms = nullptr);
-  void PollEvents();
   bool SubmitFrame(void* aTextureHandle,
                    ::vr::ETextureType aTextureType,
                    const IntSize& aSize,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
 };
 
 class VRControllerOpenVR : public VRControllerHost
@@ -122,17 +120,20 @@ private:
 
 class VRSystemManagerOpenVR : public VRSystemManager
 {
 public:
   static already_AddRefed<VRSystemManagerOpenVR> Create();
 
   virtual void Destroy() override;
   virtual void Shutdown() override;
-  virtual bool GetHMDs(nsTArray<RefPtr<VRDisplayHost> >& aHMDResult) override;
+  virtual void NotifyVSync() override;
+  virtual void Enumerate() override;
+  virtual bool ShouldInhibitEnumeration() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
   virtual void VibrateHaptic(uint32_t aControllerIdx,
                              uint32_t aHapticIndex,
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -561,22 +561,20 @@ VRDisplayPuppet::SubmitFrame(const mozil
                            const gfx::Rect& aRightEyeRect) {
 
   return false;
 }
 
 #endif
 
 void
-VRDisplayPuppet::NotifyVSync()
+VRDisplayPuppet::Refresh()
 {
-  // We update mIsConneced once per frame.
+  // We update mIsConneced once per refresh.
   mDisplayInfo.mIsConnected = true;
-
-  VRDisplayHost::NotifyVSync();
 }
 
 VRControllerPuppet::VRControllerPuppet(dom::GamepadHand aHand, uint32_t aDisplayID)
   : VRControllerHost(VRDeviceType::Puppet, aHand, aDisplayID)
   , mButtonPressState(0)
   , mButtonTouchState(0)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
@@ -698,24 +696,55 @@ VRSystemManagerPuppet::Destroy()
 }
 
 void
 VRSystemManagerPuppet::Shutdown()
 {
   mPuppetHMD = nullptr;
 }
 
-bool
-VRSystemManagerPuppet::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+void
+VRSystemManagerPuppet::NotifyVSync()
+{
+  VRSystemManager::NotifyVSync();
+  if (mPuppetHMD) {
+    mPuppetHMD->Refresh();
+  }
+}
+
+void
+VRSystemManagerPuppet::Enumerate()
 {
   if (mPuppetHMD == nullptr) {
     mPuppetHMD = new VRDisplayPuppet();
   }
-  aHMDResult.AppendElement(mPuppetHMD);
-  return true;
+}
+
+bool
+VRSystemManagerPuppet::ShouldInhibitEnumeration()
+{
+  if (VRSystemManager::ShouldInhibitEnumeration()) {
+    return true;
+  }
+  if (mPuppetHMD) {
+    // 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
+VRSystemManagerPuppet::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
+{
+  if (mPuppetHMD) {
+    aHMDResult.AppendElement(mPuppetHMD);
+  }
 }
 
 bool
 VRSystemManagerPuppet::GetIsPresenting()
 {
   if (mPuppetHMD) {
     VRDisplayInfo displayInfo(mPuppetHMD->GetDisplayInfo());
     return displayInfo.GetPresentingGroups() != kVRGroupNone;
--- a/gfx/vr/gfxVRPuppet.h
+++ b/gfx/vr/gfxVRPuppet.h
@@ -19,17 +19,16 @@ class MacIOSurface;
 namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayPuppet : public VRDisplayHost
 {
 public:
   void SetDisplayInfo(const VRDisplayInfo& aDisplayInfo);
-  virtual void NotifyVSync() override;
   void SetSensorState(const VRHMDSensorState& aSensorState);
   void ZeroSensor() override;
 
 protected:
   virtual VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
 #if defined(XP_WIN)
@@ -45,16 +44,17 @@ protected:
 #elif defined(MOZ_ANDROID_GOOGLE_VR)
   virtual bool SubmitFrame(const mozilla::layers::EGLImageDescriptor* aDescriptor,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #endif
 
 public:
   explicit VRDisplayPuppet();
+  void Refresh();
 
 protected:
   virtual ~VRDisplayPuppet();
   void Destroy();
 
   bool mIsPresenting;
 
 private:
@@ -105,29 +105,32 @@ private:
 
 class VRSystemManagerPuppet : public VRSystemManager
 {
 public:
   static already_AddRefed<VRSystemManagerPuppet> Create();
 
   virtual void Destroy() override;
   virtual void Shutdown() override;
-  virtual bool GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
+  virtual void Enumerate() override;
+  virtual bool ShouldInhibitEnumeration() override;
+  virtual void GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult) override;
   virtual bool GetIsPresenting() override;
   virtual void HandleInput() override;
   virtual void GetControllers(nsTArray<RefPtr<VRControllerHost>>&
                               aControllerResult) override;
   virtual void ScanForControllers() override;
   virtual void RemoveControllers() override;
   virtual void VibrateHaptic(uint32_t aControllerIdx,
                              uint32_t aHapticIndex,
                              double aIntensity,
                              double aDuration,
                              const VRManagerPromise& aPromise) override;
   virtual void StopVibrateHaptic(uint32_t aControllerIdx) override;
+  virtual void NotifyVSync() override;
 
 protected:
   VRSystemManagerPuppet();
 
 private:
   void HandleButtonPress(uint32_t aControllerIdx,
                          uint32_t aButton,
                          uint64_t aButtonMask,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5226,16 +5226,30 @@ pref("dom.vr.osvr.enabled", false);
 pref("dom.vr.openvr.enabled", false);
 #elif defined(XP_WIN) || defined(XP_MACOSX)
 // We enable WebVR by default for Windows and macOS
 pref("dom.vr.openvr.enabled", true);
 #else
 // See Bug 1310663 (Linux)
 pref("dom.vr.openvr.enabled", false);
 #endif
+// Minimum number of milliseconds that the browser will wait before
+// attempting to poll again for connected VR controllers.  The browser
+// will not attempt to poll for VR controllers until it needs to use them.
+pref("dom.vr.controller.enumerate.interval", 1000);
+// Minimum number of milliseconds that the browser will wait before
+// attempting to poll again for connected VR displays.  The browser
+// will not attempt to poll for VR displays until it needs to use
+// them, such as when detecting a WebVR site.
+pref("dom.vr.display.enumerate.interval", 5000);
+// Minimum number of milliseconds that the VR session will be kept
+// alive after the browser and content no longer are using the
+// hardware.  If a VR multitasking environment, this should be set
+// very low or set to 0.
+pref("dom.vr.inactive.timeout", 5000);
 // Pose prediction reduces latency effects by returning future predicted HMD
 // poses to callers of the WebVR API.  This currently only has an effect for
 // Oculus Rift on SDK 0.8 or greater.
 pref("dom.vr.poseprediction.enabled", true);
 // Starting VR presentation is only allowed within a user gesture or event such
 // as VRDisplayActivate triggered by the system.  dom.vr.require-gesture allows
 // this requirement to be disabled for special cases such as during automated
 // tests or in a headless kiosk system.