Bug 1407423 - Ensure that any time we have loaded the Oculus runtime libary, we are polling ShouldQuit draft
authorKearwood "Kip" Gilbert <kgilbert@mozilla.com>
Tue, 10 Oct 2017 14:42:37 -0700
changeset 702163 b146f885959829239818c7f3fca1696c8fa61da8
parent 702136 8697764fdb68ae0865469da5dfda1cfc3a448d24
child 702164 0c606b5a0874dcd12fe25e859e9359a09c5b0f75
push id90406
push userkgilbert@mozilla.com
push dateWed, 22 Nov 2017 21:05:43 +0000
bugs1407423
milestone59.0a1
Bug 1407423 - Ensure that any time we have loaded the Oculus runtime libary, we are polling ShouldQuit - 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
@@ -342,17 +342,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, uint32_t aPromiseID) = 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, uint32_t aPromiseID) override;
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -199,51 +199,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;
       }
@@ -253,19 +277,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;
@@ -310,16 +334,18 @@ VROculusSession::StopRendering()
   mDevice = nullptr;
 }
 
 void
 VROculusSession::StopSession()
 {
   if (mSession) {
     ovr_Destroy(mSession);
+    mIsConnected = false;
+    mIsMounted = false;
     mSession = nullptr;
   }
 }
 
 void
 VROculusSession::StopLib()
 {
   if (mInitFlags) {
@@ -332,19 +358,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
@@ -379,25 +410,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
@@ -432,17 +473,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()->GetCompositorDevice();
     if (!mDevice) {
       NS_WARNING("Failed to get a D3D11Device for Oculus");
       return false;
@@ -968,17 +1009,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;
     }
@@ -1098,17 +1139,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.
@@ -1243,28 +1284,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)
@@ -1512,47 +1545,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, uint32_t aPromiseID) override;
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -197,18 +197,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;
@@ -239,18 +241,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{};
@@ -417,27 +417,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)
@@ -628,60 +617,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,
                              uint32_t aPromiseID) 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
@@ -5152,16 +5152,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.