Bug 1362213 - Implement chrome-only API to manage VR sessions draft
authorKearwood Gilbert <kearwood@kearwood.com>
Mon, 08 May 2017 16:01:36 -0700
changeset 585332 4d3fc306332e790f29c2503e9004f9c73a0734cc
parent 585145 1bfa4578aa56f768626ba278a6929e23fc48db54
child 630709 ae8be29e358c496f14204d631d8bc2352f254153
push id61096
push userbmo:kgilbert@mozilla.com
push dateFri, 26 May 2017 20:23:01 +0000
bugs1362213
milestone55.0a1
Bug 1362213 - Implement chrome-only API to manage VR sessions - Added new chrome-only webidl methods to be used by browser UI and WebExtensions - Implemented bitmasked group visibility for VR sessions to enable switching between chrome and regular content presentations. - Implemented throttling mechanism to avoid runaway, unthrottled render loops for VR sessions that are hidden by group visibility bitmasks or due to lower level platform VR events, such as during the Oculus "Health and Safety Warning". - Simplified the PVRManager IPC protocol while extending it to support VR session groups and later WebVR content performance profiling API's. - Removed the last WebVR related sync IPC call. MozReview-Commit-ID: BMEIPyYeEbq
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/vr/VRDisplay.cpp
dom/vr/VRDisplay.h
dom/webidl/Navigator.webidl
dom/webidl/VRDisplay.webidl
gfx/vr/VRDisplayClient.cpp
gfx/vr/VRDisplayClient.h
gfx/vr/VRDisplayHost.cpp
gfx/vr/VRDisplayHost.h
gfx/vr/VRDisplayPresentation.cpp
gfx/vr/VRDisplayPresentation.h
gfx/vr/VRManager.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
gfx/vr/ipc/PVRManager.ipdl
gfx/vr/ipc/VRLayerParent.cpp
gfx/vr/ipc/VRLayerParent.h
gfx/vr/ipc/VRManagerChild.cpp
gfx/vr/ipc/VRManagerChild.h
gfx/vr/ipc/VRManagerParent.cpp
gfx/vr/ipc/VRManagerParent.h
gfx/vr/ipc/VRMessageUtils.h
gfx/vr/ovr_capi_dynamic.h
ipc/ipdl/sync-messages.ini
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -41,16 +41,17 @@
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/StorageManager.h"
 #include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/VRDisplay.h"
+#include "mozilla/dom/VRDisplayEvent.h"
 #include "mozilla/dom/VRServiceTest.h"
 #include "mozilla/dom/WebAuthentication.h"
 #include "mozilla/dom/workers/RuntimeService.h"
 #include "mozilla/Hal.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SSE.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
@@ -1562,16 +1563,37 @@ Navigator::RequestVRServiceTest()
   win->NotifyVREventListenerAdded();
 
   if (!mVRServiceTest) {
     mVRServiceTest = VRServiceTest::CreateTestService(mWindow);
   }
   return mVRServiceTest;
 }
 
+bool
+Navigator::IsWebVRContentDetected() const
+{
+  nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+  return win->IsVRContentDetected();
+}
+
+bool
+Navigator::IsWebVRContentPresenting() const
+{
+  nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+  return win->IsVRContentPresenting();
+}
+
+void
+Navigator::RequestVRPresentation(VRDisplay& aDisplay)
+{
+  nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
+  win->DispatchVRDisplayActivate(aDisplay.DisplayId(), VRDisplayEventReason::Requested);
+}
+
 //*****************************************************************************
 //    Navigator::nsIMozNavigatorNetwork
 //*****************************************************************************
 
 NS_IMETHODIMP
 Navigator::GetProperties(nsINetworkProperties** aProperties)
 {
   ErrorResult rv;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -193,16 +193,19 @@ public:
   network::Connection* GetConnection(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
 
   void GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
   GamepadServiceTest* RequestGamepadServiceTest();
   already_AddRefed<Promise> GetVRDisplays(ErrorResult& aRv);
   void GetActiveVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays) const;
   VRServiceTest* RequestVRServiceTest();
+  bool IsWebVRContentDetected() const;
+  bool IsWebVRContentPresenting() const;
+  void RequestVRPresentation(VRDisplay& aDisplay);
 #ifdef MOZ_TIME_MANAGER
   time::TimeManager* GetMozTime(ErrorResult& aRv);
 #endif // MOZ_TIME_MANAGER
 
   Presentation* GetPresentation(ErrorResult& aRv);
 
   bool SendBeacon(const nsAString& aUrl,
                   const Nullable<fetch::BodyInit>& aData,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -198,16 +198,17 @@
 
 #include "mozilla/dom/IDBFactory.h"
 #include "mozilla/dom/MessageChannel.h"
 #include "mozilla/dom/Promise.h"
 
 #include "mozilla/dom/Gamepad.h"
 #include "mozilla/dom/GamepadManager.h"
 
+#include "gfxVR.h"
 #include "mozilla/dom/VRDisplay.h"
 #include "mozilla/dom/VRDisplayEvent.h"
 #include "mozilla/dom/VRDisplayEventBinding.h"
 #include "mozilla/dom/VREventObserver.h"
 
 #include "nsRefreshDriver.h"
 #include "Layers.h"
 
@@ -1563,16 +1564,17 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
     mIsChrome(false),
     mCleanMessageManager(false),
     mNeedsFocus(true),
     mHasFocus(false),
     mShowFocusRingForContent(false),
     mFocusByKeyOccurred(false),
     mHasGamepad(false),
     mHasVREvents(false),
+    mHasVRDisplayActivateEvents(false),
     mHasSeenGamepadInput(false),
     mNotifiedIDDestroyed(false),
     mAllowScriptsToClose(false),
     mTopLevelOuterContentWindow(false),
     mSuspendDepth(0),
     mFreezeDepth(0),
     mFocusMethod(0),
     mSerial(0),
@@ -1992,23 +1994,25 @@ nsGlobalWindow::CleanUp()
     }
   }
 
   if (IsInnerWindow()) {
     DisableGamepadUpdates();
     mHasGamepad = false;
     DisableVRUpdates();
     mHasVREvents = false;
+    mHasVRDisplayActivateEvents = false;
 #ifdef MOZ_B2G
     DisableTimeChangeNotifications();
 #endif
     DisableIdleCallbackRequests();
   } else {
     MOZ_ASSERT(!mHasGamepad);
     MOZ_ASSERT(!mHasVREvents);
+    MOZ_ASSERT(!mHasVRDisplayActivateEvents);
   }
 
   if (mCleanMessageManager) {
     MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
     nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
     if (asChrome->mMessageManager) {
       static_cast<nsFrameMessageManager*>(
         asChrome->mMessageManager.get())->Disconnect();
@@ -2161,16 +2165,17 @@ nsGlobalWindow::FreeInnerObjects()
   }
   mAudioContexts.Clear();
 
   DisableGamepadUpdates();
   mHasGamepad = false;
   mGamepads.Clear();
   DisableVRUpdates();
   mHasVREvents = false;
+  mHasVRDisplayActivateEvents = false;
   mVRDisplays.Clear();
 
   if (mTabChild) {
     while (mBeforeUnloadListenerCount-- > 0) {
       mTabChild->BeforeUnloadRemoved();
     }
   }
 }
@@ -13514,16 +13519,20 @@ nsGlobalWindow::EventListenerAdded(nsIAt
   if (aType == nsGkAtoms::onvrdisplayactivate ||
       aType == nsGkAtoms::onvrdisplayconnect ||
       aType == nsGkAtoms::onvrdisplaydeactivate ||
       aType == nsGkAtoms::onvrdisplaydisconnect ||
       aType == nsGkAtoms::onvrdisplaypresentchange) {
     NotifyVREventListenerAdded();
   }
 
+  if (aType == nsGkAtoms::onvrdisplayactivate) {
+    mHasVRDisplayActivateEvents = true;
+  }
+
   if (aType == nsGkAtoms::onbeforeunload &&
       mTabChild &&
       (!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
     MOZ_ASSERT(IsInnerWindow());
     mBeforeUnloadListenerCount++;
     MOZ_ASSERT(mBeforeUnloadListenerCount > 0);
     mTabChild->BeforeUnloadAdded();
   }
@@ -13557,19 +13566,42 @@ nsGlobalWindow::NotifyVREventListenerAdd
   EnableVRUpdates();
 }
 
 bool
 nsGlobalWindow::HasUsedVR() const
 {
   MOZ_ASSERT(IsInnerWindow());
 
+  // Returns true only if any WebVR API call or related event
+  // has been used
   return mHasVREvents;
 }
 
+bool
+nsGlobalWindow::IsVRContentDetected() const
+{
+  MOZ_ASSERT(IsInnerWindow());
+
+  // Returns true only if the content will respond to
+  // the VRDisplayActivate event.
+  return mHasVRDisplayActivateEvents;
+}
+
+bool
+nsGlobalWindow::IsVRContentPresenting() const
+{
+  for (auto display : mVRDisplays) {
+    if (display->IsAnyPresenting(gfx::kVRGroupAll)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 void
 nsGlobalWindow::EnableTimeChangeNotifications()
 {
   mozilla::time::AddWindowListener(AsInner());
 }
 
 void
 nsGlobalWindow::DisableTimeChangeNotifications()
@@ -13754,17 +13786,17 @@ void
 nsGlobalWindow::DispatchVRDisplayActivate(uint32_t aDisplayID,
                                           mozilla::dom::VRDisplayEventReason aReason)
 {
   // Search for the display identified with aDisplayID and fire the
   // event if found.
   for (auto display : mVRDisplays) {
     if (display->DisplayId() == aDisplayID) {
       if (aReason != VRDisplayEventReason::Navigation &&
-          display->IsAnyPresenting()) {
+          display->IsAnyPresenting(gfx::kVRGroupContent)) {
         // We only want to trigger this event if nobody is presenting to the
         // display already or when a page is loaded by navigating away
         // from a page with an active VR Presentation.
         continue;
       }
 
       VRDisplayEventInit init;
       init.mBubbles = false;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -452,16 +452,18 @@ public:
   bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                            nsIWidget* aWidget, nsIScreen* aScreen);
   bool FullScreen() const;
 
   // Inner windows only.
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override;
   void NotifyVREventListenerAdded();
   bool HasUsedVR() const;
+  bool IsVRContentDetected() const;
+  bool IsVRContentPresenting() const;
 
   using EventTarget::EventListenerAdded;
   virtual void EventListenerAdded(nsIAtom* aType) override;
   using EventTarget::EventListenerRemoved;
   virtual void EventListenerRemoved(nsIAtom* aType) override;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
@@ -1902,16 +1904,20 @@ protected:
 
   // Inner windows only.
   // Indicates whether this window wants gamepad input events
   bool                   mHasGamepad : 1;
 
   // Inner windows only.
   // Indicates whether this window wants VR events
   bool                   mHasVREvents : 1;
+
+  // Inner windows only.
+  // Indicates whether this window wants VRDisplayActivate events
+  bool                   mHasVRDisplayActivateEvents : 1;
   nsCheapSet<nsUint32HashKey> mGamepadIndexSet;
   nsRefPtrHashtable<nsUint32HashKey, mozilla::dom::Gamepad> mGamepads;
   bool mHasSeenGamepadInput;
 
   // whether we've sent the destroy notification for our window id
   bool                   mNotifiedIDDestroyed : 1;
   // whether scripts may close the window,
   // even if "dom.allow_scripts_to_close_windows" is false.
--- a/dom/vr/VRDisplay.cpp
+++ b/dom/vr/VRDisplay.cpp
@@ -530,34 +530,42 @@ VRDisplay::RequestPresent(const nsTArray
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
+  bool isChromePresentation = aCallerType == CallerType::System;
+  uint32_t presentationGroup = isChromePresentation ? gfx::kVRGroupChrome : gfx::kVRGroupContent;
+
   if (!EventStateManager::IsHandlingUserInput() &&
-      aCallerType != CallerType::System &&
+      !isChromePresentation &&
       !IsHandlingVRNavigationEvent() &&
       gfxPrefs::VRRequireGesture()) {
     // The WebVR API states that if called outside of a user gesture, the
     // promise must be rejected.  We allow VR presentations to start within
     // trusted events such as vrdisplayactivate, which triggers in response to
     // HMD proximity sensors and when navigating within a VR presentation.
+    // This user gesture requirement is not enforced for chrome/system code.
     promise->MaybeRejectWithUndefined();
-  } else if (!IsPresenting() && IsAnyPresenting()) {
-    // Only one presentation allowed per VRDisplay
-    // on a first-come-first-serve basis.
+  } else if (!IsPresenting() && IsAnyPresenting(presentationGroup)) {
+    // Only one presentation allowed per VRDisplay on a
+    // first-come-first-serve basis.
     // If this Javascript context is presenting, then we can replace our
     // presentation with a new one containing new layers but we should never
     // replace the presentation of another context.
+    // Simultaneous presentations in other groups are allowed in separate
+    // Javascript contexts to enable browser UI from chrome/system contexts.
+    // Eventually, this restriction will be loosened to enable multitasking
+    // use cases.
     promise->MaybeRejectWithUndefined();
   } else {
-    mPresentation = mClient->BeginPresentation(aLayers);
+    mPresentation = mClient->BeginPresentation(aLayers, presentationGroup);
     mFrameInfo.Clear();
     promise->MaybeResolve(JS::UndefinedHandleValue);
   }
   return promise.forget();
 }
 
 NS_IMETHODIMP
 VRDisplay::Observe(nsISupports* aSubject, const char* aTopic,
@@ -672,29 +680,54 @@ bool
 VRDisplay::IsPresenting() const
 {
   // IsPresenting returns true only if this Javascript context is presenting
   // and will return false if another context is presenting.
   return mPresentation != nullptr;
 }
 
 bool
-VRDisplay::IsAnyPresenting() const
+VRDisplay::IsAnyPresenting(uint32_t aGroupMask) const
 {
-  // IsAnyPresenting returns true if any Javascript context is presenting
-  // even if this context is not presenting.
-  return IsPresenting() || mClient->GetIsPresenting();
+  // IsAnyPresenting returns true if either this VRDisplay object or any other
+  // from anther Javascript context is presenting with a group matching
+  // aGroupMask.
+  if (mPresentation && (mPresentation->GetGroup() & aGroupMask)) {
+    return true;
+  }
+  if (mClient->GetDisplayInfo().GetPresentingGroups() & aGroupMask) {
+    return true;
+  }
+  return false;
 }
 
 bool
 VRDisplay::IsConnected() const
 {
   return mClient->GetIsConnected();
 }
 
+uint32_t
+VRDisplay::PresentingGroups() const
+{
+  return mClient->GetDisplayInfo().GetPresentingGroups();
+}
+
+uint32_t
+VRDisplay::GroupMask() const
+{
+  return mClient->GetDisplayInfo().GetGroupMask();
+}
+
+void
+VRDisplay::SetGroupMask(const uint32_t& aGroupMask)
+{
+  mClient->SetGroupMask(aGroupMask);
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(VRDisplay, DOMEventTargetHelper, mCapabilities, mStageParameters)
 
 NS_IMPL_ADDREF_INHERITED(VRDisplay, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(VRDisplay, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRDisplay)
 NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, DOMEventTargetHelper)
--- a/dom/vr/VRDisplay.h
+++ b/dom/vr/VRDisplay.h
@@ -303,18 +303,21 @@ class VRDisplay final : public DOMEventT
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIOBSERVER
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRDisplay, DOMEventTargetHelper)
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
+  uint32_t PresentingGroups() const;
+  uint32_t GroupMask() const;
+  void SetGroupMask(const uint32_t& aGroupMask);
+  bool IsAnyPresenting(uint32_t aGroupMask) const;
   bool IsPresenting() const;
-  bool IsAnyPresenting() const;
   bool IsConnected() const;
 
   VRDisplayCapabilities* Capabilities();
   VRStageParameters* GetStageParameters();
 
   uint32_t DisplayId() const { return mDisplayId; }
   void GetDisplayName(nsAString& aDisplayName) const { aDisplayName = mDisplayName; }
 
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -268,16 +268,22 @@ partial interface Navigator {
 };
 
 partial interface Navigator {
   [Throws, Pref="dom.vr.enabled"]
   Promise<sequence<VRDisplay>> getVRDisplays();
   // TODO: Use FrozenArray once available. (Bug 1236777)
   [Frozen, Cached, Pure, Pref="dom.vr.enabled"]
   readonly attribute sequence<VRDisplay> activeVRDisplays;
+  [ChromeOnly, Pref="dom.vr.enabled"]
+  readonly attribute boolean isWebVRContentDetected;
+  [ChromeOnly, Pref="dom.vr.enabled"]
+  readonly attribute boolean isWebVRContentPresenting;
+  [ChromeOnly, Pref="dom.vr.enabled"]
+  void requestVRPresentation(VRDisplay display);
 };
 partial interface Navigator {
   [Pref="dom.vr.test.enabled"]
   VRServiceTest requestVRServiceTest();
 };
 
 #ifdef MOZ_TIME_MANAGER
 // nsIDOMMozNavigatorTime
--- a/dom/webidl/VRDisplay.webidl
+++ b/dom/webidl/VRDisplay.webidl
@@ -178,16 +178,36 @@ interface VREyeParameters {
    */
   [Constant] readonly attribute unsigned long renderWidth;
   [Constant] readonly attribute unsigned long renderHeight;
 };
 
 [Pref="dom.vr.enabled",
  HeaderFile="mozilla/dom/VRDisplay.h"]
 interface VRDisplay : EventTarget {
+  /**
+   * presentingGroups is a bitmask indicating which VR session groups
+   * have an active VR presentation.
+   */
+  [ChromeOnly] readonly attribute unsigned long presentingGroups;
+  /**
+   * Setting groupMask causes submitted frames by VR sessions that
+   * aren't included in the bitmasked groups to be ignored.
+   * Non-chrome content is not aware of the value of groupMask.
+   * VRDisplay.RequestAnimationFrame will still fire for VR sessions
+   * that are hidden by groupMask, enabling their performance to be
+   * measured by chrome UI that is presented in other groups.
+   * This is expected to be used in cases where chrome UI is presenting
+   * information during link traversal or presenting options when content
+   * performance is too low for comfort.
+   * The VR refresh / VSync cycle is driven by the visible content
+   * and the non-visible content may have a throttled refresh rate.
+   */
+  [ChromeOnly] attribute unsigned long groupMask;
+
   readonly attribute boolean isConnected;
   readonly attribute boolean isPresenting;
 
   /**
    * Dictionary of capabilities describing the VRDisplay.
    */
   [Constant] readonly attribute VRDisplayCapabilities capabilities;
 
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -27,83 +27,67 @@
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 VRDisplayClient::VRDisplayClient(const VRDisplayInfo& aDisplayInfo)
   : mDisplayInfo(aDisplayInfo)
   , bLastEventWasMounted(false)
   , bLastEventWasPresenting(false)
   , mPresentationCount(0)
+  , mLastEventFrameId(0)
 {
   MOZ_COUNT_CTOR(VRDisplayClient);
 }
 
 VRDisplayClient::~VRDisplayClient() {
   MOZ_COUNT_DTOR(VRDisplayClient);
 }
 
 void
 VRDisplayClient::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo)
 {
   mDisplayInfo = aDisplayInfo;
+  FireEvents();
 }
 
 already_AddRefed<VRDisplayPresentation>
-VRDisplayClient::BeginPresentation(const nsTArray<mozilla::dom::VRLayer>& aLayers)
+VRDisplayClient::BeginPresentation(const nsTArray<mozilla::dom::VRLayer>& aLayers,
+                                   uint32_t aGroup)
 {
   ++mPresentationCount;
-  RefPtr<VRDisplayPresentation> presentation = new VRDisplayPresentation(this, aLayers);
+  RefPtr<VRDisplayPresentation> presentation = new VRDisplayPresentation(this, aLayers, aGroup);
   return presentation.forget();
 }
 
 void
 VRDisplayClient::PresentationDestroyed()
 {
   --mPresentationCount;
 }
 
 void
 VRDisplayClient::ZeroSensor()
 {
   VRManagerChild *vm = VRManagerChild::Get();
   vm->SendResetSensor(mDisplayInfo.mDisplayID);
 }
 
-VRHMDSensorState
-VRDisplayClient::GetSensorState()
-{
-  VRHMDSensorState sensorState;
-  VRManagerChild *vm = VRManagerChild::Get();
-  Unused << vm->SendGetSensorState(mDisplayInfo.mDisplayID, &sensorState);
-  return sensorState;
-}
-
-const double kVRDisplayRAFMaxDuration = 32; // milliseconds
-
 void
-VRDisplayClient::NotifyVsync()
+VRDisplayClient::SetGroupMask(uint32_t aGroupMask)
 {
   VRManagerChild *vm = VRManagerChild::Get();
-
-  bool isPresenting = GetIsPresenting();
+  vm->SendSetGroupMask(mDisplayInfo.mDisplayID, aGroupMask);
+}
 
-  bool bShouldCallback = !isPresenting;
-  if (mLastVSyncTime.IsNull()) {
-    bShouldCallback = true;
-  } else {
-    TimeDuration duration = TimeStamp::Now() - mLastVSyncTime;
-    if (duration.ToMilliseconds() > kVRDisplayRAFMaxDuration) {
-      bShouldCallback = true;
-    }
-  }
-
-  if (bShouldCallback) {
-    vm->RunFrameRequestCallbacks();
-    mLastVSyncTime = TimeStamp::Now();
-  }
+void
+VRDisplayClient::FireEvents()
+{
+  VRManagerChild *vm = VRManagerChild::Get();
+  // Only fire these events for non-chrome VR sessions
+  bool isPresenting = (mDisplayInfo.mPresentingGroups & kVRGroupContent) != 0;
 
   // Check if we need to trigger onVRDisplayPresentChange event
   if (bLastEventWasPresenting != isPresenting) {
     bLastEventWasPresenting = isPresenting;
     vm->FireDOMVRDisplayPresentChangeEvent(mDisplayInfo.mDisplayID);
   }
 
   // Check if we need to trigger onvrdisplayactivate event
@@ -116,38 +100,36 @@ VRDisplayClient::NotifyVsync()
 
   // Check if we need to trigger onvrdisplaydeactivate event
   if (bLastEventWasMounted && !mDisplayInfo.mIsMounted) {
     bLastEventWasMounted = false;
     if (gfxPrefs::VRAutoActivateEnabled()) {
       vm->FireDOMVRDisplayUnmountedEvent(mDisplayInfo.mDisplayID);
     }
   }
+
+  // Check if we need to trigger VRDisplay.requestAnimationFrame
+  if (mLastEventFrameId != mDisplayInfo.mFrameId) {
+    mLastEventFrameId = mDisplayInfo.mFrameId;
+    vm->RunFrameRequestCallbacks();
+  }
 }
 
-void
-VRDisplayClient::NotifyVRVsync()
+VRHMDSensorState
+VRDisplayClient::GetSensorState()
 {
-  VRManagerChild *vm = VRManagerChild::Get();
-  vm->RunFrameRequestCallbacks();
-  mLastVSyncTime = TimeStamp::Now();
+  return mDisplayInfo.GetSensorState();
 }
 
 bool
 VRDisplayClient::GetIsConnected() const
 {
   return mDisplayInfo.GetIsConnected();
 }
 
-bool
-VRDisplayClient::GetIsPresenting() const
-{
-  return mDisplayInfo.GetIsPresenting();
-}
-
 void
 VRDisplayClient::NotifyDisconnected()
 {
   mDisplayInfo.mIsConnected = false;
 }
 
 void
 VRDisplayClient::UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult)
--- a/gfx/vr/VRDisplayClient.h
+++ b/gfx/vr/VRDisplayClient.h
@@ -29,38 +29,37 @@ public:
   void UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult);
 
   const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
   virtual VRHMDSensorState GetSensorState();
   void GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult);
 
   virtual void ZeroSensor();
 
-  already_AddRefed<VRDisplayPresentation> BeginPresentation(const nsTArray<dom::VRLayer>& aLayers);
+  already_AddRefed<VRDisplayPresentation> BeginPresentation(const nsTArray<dom::VRLayer>& aLayers,
+                                                            uint32_t aGroup);
   void PresentationDestroyed();
 
-  void NotifyVsync();
-  void NotifyVRVsync();
-
   bool GetIsConnected() const;
-  bool GetIsPresenting() const;
 
   void NotifyDisconnected();
+  void SetGroupMask(uint32_t aGroupMask);
 
 protected:
   virtual ~VRDisplayClient();
 
+  void FireEvents();
+
   VRDisplayInfo mDisplayInfo;
 
   bool bLastEventWasMounted;
   bool bLastEventWasPresenting;
 
-  TimeStamp mLastVSyncTime;
   int mPresentationCount;
-
+  uint32_t mLastEventFrameId;
 private:
   VRSubmitFrameResultInfo mSubmitFrameResult;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_DISPLAY_CLIENT_H */
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -1,101 +1,165 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VRDisplayHost.h"
 #include "gfxVR.h"
+#include "ipc/VRLayerParent.h"
 
 #if defined(XP_WIN)
 
 #include <d3d11.h>
 #include "gfxWindowsPlatform.h"
 #include "../layers/d3d11/CompositorD3D11.h"
 #include "mozilla/layers/TextureD3D11.h"
 
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::layers;
 
 VRDisplayHost::VRDisplayHost(VRDeviceType aType)
-  : mInputFrameID(0)
 {
   MOZ_COUNT_CTOR(VRDisplayHost);
   mDisplayInfo.mType = aType;
   mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
-  mDisplayInfo.mIsPresenting = false;
+  mDisplayInfo.mPresentingGroups = 0;
+  mDisplayInfo.mGroupMask = kVRGroupContent;
+  mDisplayInfo.mFrameId = 0;
 }
 
 VRDisplayHost::~VRDisplayHost()
 {
   MOZ_COUNT_DTOR(VRDisplayHost);
 }
 
 void
+VRDisplayHost::SetGroupMask(uint32_t aGroupMask)
+{
+  mDisplayInfo.mGroupMask = aGroupMask;
+}
+
+void
 VRDisplayHost::AddLayer(VRLayerParent *aLayer)
 {
   mLayers.AppendElement(aLayer);
+  mDisplayInfo.mPresentingGroups &= aLayer->GetGroup();
   if (mLayers.Length() == 1) {
     StartPresentation();
   }
-  mDisplayInfo.mIsPresenting = mLayers.Length() > 0;
 
   // Ensure that the content process receives the change immediately
   VRManager* vm = VRManager::Get();
   vm->RefreshVRDisplays();
 }
 
 void
 VRDisplayHost::RemoveLayer(VRLayerParent *aLayer)
 {
   mLayers.RemoveElement(aLayer);
   if (mLayers.Length() == 0) {
     StopPresentation();
   }
-  mDisplayInfo.mIsPresenting = mLayers.Length() > 0;
+  mDisplayInfo.mGroupMask = 0;
+  for (auto layer : mLayers) {
+    mDisplayInfo.mGroupMask &= layer->GetGroup();
+  }
 
   // Ensure that the content process receives the change immediately
   VRManager* vm = VRManager::Get();
   vm->RefreshVRDisplays();
 }
 
+void
+VRDisplayHost::StartFrame()
+{
+  mLastFrameStart = TimeStamp::Now();
+  ++mDisplayInfo.mFrameId;
+  mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames] = GetSensorState();
+}
+
+void
+VRDisplayHost::NotifyVSync()
+{
+  /**
+   * We will trigger a new frame immediately after a successful frame texture
+   * submission.  If content fails to call VRDisplay.submitFrame after
+   * kVRDisplayRAFMaxDuration milliseconds has elapsed since the last
+   * VRDisplay.requestAnimationFrame, we act as a "watchdog" and kick-off
+   * a new VRDisplay.requestAnimationFrame to avoid a render loop stall and
+   * to give content a chance to recover.
+   *
+   * If the lower level VR platform API's are rejecting submitted frames,
+   * such as when the Oculus "Health and Safety Warning" is displayed,
+   * we will not kick off the next frame immediately after VRDisplay.submitFrame
+   * as it would result in an unthrottled render loop that would free run at
+   * potentially extreme frame rates.  To ensure that content has a chance to
+   * resume its presentation when the frames are accepted once again, we rely
+   * on this "watchdog" to act as a VR refresh driver cycling at a rate defined
+   * by kVRDisplayRAFMaxDuration.
+   * 
+   * kVRDisplayRAFMaxDuration is the number of milliseconds since last frame
+   * start before triggering a new frame.  When content is failing to submit
+   * frames on time or the lower level VR platform API's are rejecting frames,
+   * kVRDisplayRAFMaxDuration determines the rate at which RAF callbacks
+   * will be called.
+   *
+   * This number must be larger than the slowest expected frame time during
+   * normal VR presentation, but small enough not to break content that
+   * makes assumptions of reasonably minimal VSync rate.
+   *
+   * The slowest expected refresh rate for a VR display currently is an
+   * Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz.
+   * A kVRDisplayRAFMaxDuration value of 50 milliseconds results in a 20hz
+   * rate, which avoids inadvertent triggering of the watchdog during
+   * Oculus ASW even if every second frame is dropped.
+   */
+  const double kVRDisplayRAFMaxDuration = 50;
+
+  bool bShouldStartFrame = false;
+
+  if (mDisplayInfo.mPresentingGroups == 0) {
+    // If this display isn't presenting, refresh the sensors and trigger
+    // VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
+    bShouldStartFrame = true;
+  } else {
+    // If content fails to call VRDisplay.submitFrame, we must eventually
+    // time-out and trigger a new frame.
+    if (mLastFrameStart.IsNull()) {
+      bShouldStartFrame = true;
+    } else {
+      TimeDuration duration = TimeStamp::Now() - mLastFrameStart;
+      if (duration.ToMilliseconds() > kVRDisplayRAFMaxDuration) {
+        bShouldStartFrame = true;
+      }
+    }
+  }
+
+  if (bShouldStartFrame) {
+    VRManager *vm = VRManager::Get();
+    MOZ_ASSERT(vm);
+    vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+  }
+}
+
 #if defined(XP_WIN)
 
 void
-VRDisplayHost::SubmitFrame(VRLayerParent* aLayer, const int32_t& aInputFrameID,
-  PTextureParent* aTexture, const gfx::Rect& aLeftEyeRect,
-  const gfx::Rect& aRightEyeRect)
+VRDisplayHost::SubmitFrame(VRLayerParent* aLayer, PTextureParent* aTexture,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect)
 {
-  // aInputFrameID is no longer controlled by content with the WebVR 1.1 API
-  // update; however, we will later use this code to enable asynchronous
-  // submission of multiple layers to be composited.  This will enable
-  // us to build browser UX that remains responsive even when content does
-  // not consistently submit frames.
-
-  int32_t inputFrameID = aInputFrameID;
-  if (inputFrameID == 0) {
-    inputFrameID = mInputFrameID;
+  if (mDisplayInfo.mGroupMask & aLayer->GetGroup()) {
+    // Suppress layers hidden by the group mask
+    return;
   }
-  if (inputFrameID < 0) {
-    // Sanity check to prevent invalid memory access on builds with assertions
-    // disabled.
-    inputFrameID = 0;
-  }
-
-  VRHMDSensorState sensorState = mLastSensorState[inputFrameID % kMaxLatencyFrames];
-  // It is possible to get a cache miss on mLastSensorState if latency is
-  // longer than kMaxLatencyFrames.  An optimization would be to find a frame
-  // that is closer than the one selected with the modulus.
-  // If we hit this; however, latency is already so high that the site is
-  // un-viewable and a more accurate pose prediction is not likely to
-  // compensate.
 
   TextureHost* th = TextureHost::AsTextureHost(aTexture);
   // WebVR doesn't use the compositor to compose the frame, so use
   // AutoLockTextureHostWithoutCompositor here.
   AutoLockTextureHostWithoutCompositor autoLock(th);
   if (autoLock.Failed()) {
     NS_WARNING("Failed to lock the VR layer texture");
     return;
@@ -111,25 +175,41 @@ VRDisplayHost::SubmitFrame(VRLayerParent
   IntSize texSize = source->GetSize();
 
   TextureSourceD3D11* sourceD3D11 = source->AsSourceD3D11();
   if (!sourceD3D11) {
     NS_WARNING("WebVR support currently only implemented for D3D11");
     return;
   }
 
-  SubmitFrame(sourceD3D11, texSize, sensorState, aLeftEyeRect, aRightEyeRect);
+  if (!SubmitFrame(sourceD3D11, texSize, aLeftEyeRect, aRightEyeRect)) {
+    return;
+  }
+
+  /**
+   * Trigger the next VSync immediately after we are successfully
+   * submitting frames.  As SubmitFrame is responsible for throttling
+   * the render loop, if we don't successfully call it, we shouldn't trigger
+   * NotifyVRVsync immediately, as it will run unbounded.
+   * If NotifyVRVsync is not called here due to SubmitFrame failing, the
+   * fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause
+   * frames to continue at a lower refresh rate until frame submission
+   * succeeds again.
+   */
+  VRManager *vm = VRManager::Get();
+  MOZ_ASSERT(vm);
+  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
 }
 
 #else
 
 void
-VRDisplayHost::SubmitFrame(VRLayerParent* aLayer, const int32_t& aInputFrameID,
-  PTextureParent* aTexture, const gfx::Rect& aLeftEyeRect,
-  const gfx::Rect& aRightEyeRect)
+VRDisplayHost::SubmitFrame(VRLayerParent* aLayer, PTextureParent* aTexture,
+                           const gfx::Rect& aLeftEyeRect,
+                           const gfx::Rect& aRightEyeRect)
 {
   NS_WARNING("WebVR only supported in Windows.");
 }
 
 #endif
 
 bool
 VRDisplayHost::CheckClearDisplayInfoDirty()
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -32,60 +32,58 @@ class VRDisplayHost {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayHost)
 
   const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; }
 
   void AddLayer(VRLayerParent* aLayer);
   void RemoveLayer(VRLayerParent* aLayer);
 
-  virtual VRHMDSensorState GetSensorState() = 0;
+  
   virtual void ZeroSensor() = 0;
   virtual void StartPresentation() = 0;
   virtual void StopPresentation() = 0;
-  virtual void NotifyVSync() { };
+  virtual void NotifyVSync();
 
+  void StartFrame();
   void SubmitFrame(VRLayerParent* aLayer,
-                   const int32_t& aInputFrameID,
                    mozilla::layers::PTextureParent* aTexture,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
 
   bool CheckClearDisplayInfoDirty();
+  void SetGroupMask(uint32_t aGroupMask);
 
 protected:
   explicit VRDisplayHost(VRDeviceType aType);
   virtual ~VRDisplayHost();
 
 #if defined(XP_WIN)
-  virtual void SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
+  // Subclasses should override this SubmitFrame function.
+  // Returns true if the SubmitFrame call will block as necessary
+  // to control timing of the next frame and throttle the render loop
+  // for the needed framerate.
+  virtual bool SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
                            const IntSize& aSize,
-                           const VRHMDSensorState& aSensorState,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) = 0;
 #endif
 
   VRDisplayInfo mDisplayInfo;
 
   nsTArray<RefPtr<VRLayerParent>> mLayers;
-  // Weak reference to mLayers entries are cleared in VRLayerParent destructor
+  // Weak reference to mLayers entries are cleared in
+  // VRLayerParent destructor
 
-  // The maximum number of frames of latency that we would expect before we
-  // should give up applying pose prediction.
-  // If latency is greater than one second, then the experience is not likely
-  // to be corrected by pose prediction.  Setting this value too
-  // high may result in unnecessary memory allocation.
-  // As the current fastest refresh rate is 90hz, 100 is selected as a
-  // conservative value.
-  static const int kMaxLatencyFrames = 100;
-  VRHMDSensorState mLastSensorState[kMaxLatencyFrames];
-  int32_t mInputFrameID;
+protected:
+  virtual VRHMDSensorState GetSensorState() = 0;
 
 private:
   VRDisplayInfo mLastUpdateDisplayInfo;
+  TimeStamp mLastFrameStart;
 };
 
 class VRControllerHost {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRControllerHost)
 
   const VRControllerInfo& GetControllerInfo() const;
   void SetButtonPressed(uint64_t aBit);
--- a/gfx/vr/VRDisplayPresentation.cpp
+++ b/gfx/vr/VRDisplayPresentation.cpp
@@ -9,23 +9,31 @@
 #include "mozilla/Unused.h"
 #include "VRDisplayClient.h"
 #include "VRLayerChild.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 VRDisplayPresentation::VRDisplayPresentation(VRDisplayClient *aDisplayClient,
-                                             const nsTArray<mozilla::dom::VRLayer>& aLayers)
+                                             const nsTArray<mozilla::dom::VRLayer>& aLayers,
+                                             uint32_t aGroup)
   : mDisplayClient(aDisplayClient)
   , mDOMLayers(aLayers)
+  , mGroup(aGroup)
 {
   CreateLayers();
 }
 
+uint32_t
+VRDisplayPresentation::GetGroup() const
+{
+  return mGroup;
+}
+
 void
 VRDisplayPresentation::CreateLayers()
 {
   if (mLayers.Length()) {
     return;
   }
 
   for (dom::VRLayer& layer : mDOMLayers) {
@@ -75,17 +83,18 @@ VRDisplayPresentation::CreateLayers()
     nsIDocument* doc;
     doc = canvasElement->OwnerDoc();
     if (doc) {
       target = doc->EventTargetFor(TaskCategory::Other);
     }
 
     RefPtr<VRLayerChild> vrLayer =
       static_cast<VRLayerChild*>(manager->CreateVRLayer(mDisplayClient->GetDisplayInfo().GetDisplayID(),
-                                                        leftBounds, rightBounds, target));
+                                                        leftBounds, rightBounds, target,
+                                                        mGroup));
     if (!vrLayer) {
       NS_WARNING("CreateVRLayer returned null!");
       continue;
     }
 
     vrLayer->Initialize(canvasElement);
 
     mLayers.AppendElement(vrLayer);
--- a/gfx/vr/VRDisplayPresentation.h
+++ b/gfx/vr/VRDisplayPresentation.h
@@ -14,26 +14,30 @@ namespace gfx {
 class VRDisplayClient;
 class VRLayerChild;
 
 class VRDisplayPresentation final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayPresentation)
 
 public:
-  VRDisplayPresentation(VRDisplayClient *aDisplayClient, const nsTArray<dom::VRLayer>& aLayers);
+  VRDisplayPresentation(VRDisplayClient *aDisplayClient,
+                        const nsTArray<dom::VRLayer>& aLayers,
+                        uint32_t aGroup);
   void SubmitFrame();
   void GetDOMLayers(nsTArray<dom::VRLayer>& result);
+  uint32_t GetGroup() const;
 
 private:
   ~VRDisplayPresentation();
   void CreateLayers();
   void DestroyLayers();
 
   RefPtr<VRDisplayClient> mDisplayClient;
   nsTArray<dom::VRLayer> mDOMLayers;
   nsTArray<RefPtr<VRLayerChild>> mLayers;
+  uint32_t mGroup;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_DISPLAY_PRESENTAITON_H */
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -163,25 +163,27 @@ VRManager::NotifyVsync(const TimeStamp& 
   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();
-    if (mVRDisplays.Count()) {
-      Unused << vmp->SendNotifyVSync();
-    }
     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()) {
-    gfx::VRDisplayHost* display = iter.UserData();
+    displays.AppendElement(iter.UserData());
+  }
+  for (const auto& display: displays) {
     display->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
@@ -234,19 +236,22 @@ void
 VRManager::NotifyVRVsync(const uint32_t& aDisplayID)
 {
   for (const auto& manager: mManagers) {
     if (manager->GetIsPresenting()) {
       manager->HandleInput();
     }
   }
 
-  for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) {
-    Unused << iter.Get()->GetKey()->SendNotifyVRVSync(aDisplayID);
+  RefPtr<VRDisplayHost> display = GetDisplay(aDisplayID);
+  if (display) {
+    display->StartFrame();
   }
+
+  RefreshVRDisplays();
 }
 
 void
 VRManager::RefreshVRDisplays(bool aMustDispatch)
 {
   nsTArray<RefPtr<gfx::VRDisplayHost> > displays;
 
   /** We don't wish to enumerate the same display from multiple managers,
@@ -334,17 +339,17 @@ void
 VRManager::SubmitFrame(VRLayerParent* aLayer, layers::PTextureParent* aTexture,
                        const gfx::Rect& aLeftEyeRect,
                        const gfx::Rect& aRightEyeRect)
 {
   TextureHost* th = TextureHost::AsTextureHost(aTexture);
   mLastFrame = th;
   RefPtr<VRDisplayHost> display = GetDisplay(aLayer->GetDisplayID());
   if (display) {
-    display->SubmitFrame(aLayer, 0, aTexture, aLeftEyeRect, aRightEyeRect);
+    display->SubmitFrame(aLayer, aTexture, aLeftEyeRect, aRightEyeRect);
   }
 }
 
 RefPtr<gfx::VRControllerHost>
 VRManager::GetController(const uint32_t& aControllerID)
 {
   RefPtr<gfx::VRControllerHost> controller;
   if (mVRControllers.Get(aControllerID, getter_AddRefs(controller))) {
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -127,90 +127,138 @@ struct VRFieldOfView {
   Matrix4x4 ConstructProjectionMatrix(float zNear, float zFar, bool rightHanded) const;
 
   double upDegrees;
   double rightDegrees;
   double downDegrees;
   double leftDegrees;
 };
 
+struct VRHMDSensorState {
+  VRHMDSensorState()
+  {
+    Clear();
+  }
+  int32_t inputFrameID;
+  double timestamp;
+  VRDisplayCapabilityFlags flags;
+
+  // These members will only change with inputFrameID:
+  float orientation[4];
+  float position[3];
+  float angularVelocity[3];
+  float angularAcceleration[3];
+  float linearVelocity[3];
+  float linearAcceleration[3];
+
+  void Clear() {
+    memset(this, 0, sizeof(VRHMDSensorState));
+  }
+
+  bool operator==(const VRHMDSensorState& other) const {
+    return inputFrameID == other.inputFrameID &&
+           timestamp == other.timestamp;
+  }
+
+  bool operator!=(const VRHMDSensorState& other) const {
+    return !(*this == other);
+  }
+};
+
+// The maximum number of frames of latency that we would expect before we
+// should give up applying pose prediction.
+// If latency is greater than one second, then the experience is not likely
+// to be corrected by pose prediction.  Setting this value too
+// high may result in unnecessary memory allocation.
+// As the current fastest refresh rate is 90hz, 100 is selected as a
+// conservative value.
+static const int kVRMaxLatencyFrames = 100;
+
+// We assign VR presentations to groups with a bitmask.
+// Currently, we will only display either content or chrome.
+// Later, we will have more groups to support VR home spaces and
+// multitasking environments.
+// These values are not exposed to regular content and only affect
+// chrome-only API's.  They may be changed at any time.
+static const uint32_t kVRGroupNone = 0;
+static const uint32_t kVRGroupContent = 1 << 0;
+static const uint32_t kVRGroupChrome = 1 << 1;
+static const uint32_t kVRGroupAll = 0xffffffff;
+
 struct VRDisplayInfo
 {
   VRDeviceType GetType() const { return mType; }
   uint32_t GetDisplayID() const { return mDisplayID; }
   const nsCString& GetDisplayName() const { return mDisplayName; }
   VRDisplayCapabilityFlags GetCapabilities() const { return mCapabilityFlags; }
 
   const IntSize& SuggestedEyeResolution() const { return mEyeResolution; }
   const Point3D& GetEyeTranslation(uint32_t whichEye) const { return mEyeTranslation[whichEye]; }
   const VRFieldOfView& GetEyeFOV(uint32_t whichEye) const { return mEyeFOV[whichEye]; }
   bool GetIsConnected() const { return mIsConnected; }
   bool GetIsMounted() const { return mIsMounted; }
-  bool GetIsPresenting() const { return mIsPresenting; }
+  uint32_t GetPresentingGroups() const { return mPresentingGroups; }
+  uint32_t GetGroupMask() const { return mGroupMask; }
   const Size& GetStageSize() const { return mStageSize; }
   const Matrix4x4& GetSittingToStandingTransform() const { return mSittingToStandingTransform; }
+  uint32_t GetFrameId() const { return mFrameId; }
 
   enum Eye {
     Eye_Left,
     Eye_Right,
     NumEyes
   };
 
   uint32_t mDisplayID;
   VRDeviceType mType;
   nsCString mDisplayName;
   VRDisplayCapabilityFlags mCapabilityFlags;
   VRFieldOfView mEyeFOV[VRDisplayInfo::NumEyes];
   Point3D mEyeTranslation[VRDisplayInfo::NumEyes];
   IntSize mEyeResolution;
   bool mIsConnected;
   bool mIsMounted;
-  bool mIsPresenting;
+  uint32_t mPresentingGroups;
+  uint32_t mGroupMask;
   Size mStageSize;
   Matrix4x4 mSittingToStandingTransform;
+  uint32_t mFrameId;
+  VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames];
 
   bool operator==(const VRDisplayInfo& other) const {
+    for (size_t i = 0; i < kVRMaxLatencyFrames; i++) {
+      if (mLastSensorState[i] != other.mLastSensorState[i]) {
+        return false;
+      }
+    }
     return mType == other.mType &&
            mDisplayID == other.mDisplayID &&
            mDisplayName == other.mDisplayName &&
            mCapabilityFlags == other.mCapabilityFlags &&
            mEyeResolution == other.mEyeResolution &&
            mIsConnected == other.mIsConnected &&
            mIsMounted == other.mIsMounted &&
-           mIsPresenting == other.mIsPresenting &&
+           mPresentingGroups == other.mPresentingGroups &&
+           mGroupMask == other.mGroupMask &&
            mEyeFOV[0] == other.mEyeFOV[0] &&
            mEyeFOV[1] == other.mEyeFOV[1] &&
            mEyeTranslation[0] == other.mEyeTranslation[0] &&
            mEyeTranslation[1] == other.mEyeTranslation[1] &&
            mStageSize == other.mStageSize &&
-           mSittingToStandingTransform == other.mSittingToStandingTransform;
+           mSittingToStandingTransform == other.mSittingToStandingTransform &&
+           mFrameId == other.mFrameId;
   }
 
   bool operator!=(const VRDisplayInfo& other) const {
     return !(*this == other);
   }
-};
 
-struct VRHMDSensorState {
-  VRHMDSensorState()
+  const VRHMDSensorState& GetSensorState() const
   {
-    Clear();
-  }
-  double timestamp;
-  int32_t inputFrameID;
-  VRDisplayCapabilityFlags flags;
-  float orientation[4];
-  float position[3];
-  float angularVelocity[3];
-  float angularAcceleration[3];
-  float linearVelocity[3];
-  float linearAcceleration[3];
-
-  void Clear() {
-    memset(this, 0, sizeof(VRHMDSensorState));
+    return mLastSensorState[mFrameId % kVRMaxLatencyFrames];
   }
 };
 
 struct VRSubmitFrameResultInfo
 {
   VRSubmitFrameResultInfo()
    : mFrameNum(0),
      mWidth(0),
--- a/gfx/vr/gfxVROSVR.cpp
+++ b/gfx/vr/gfxVROSVR.cpp
@@ -294,16 +294,17 @@ VRDisplayOSVR::GetSensorState()
   OSVR_TimeValue timestamp;
 
   OSVR_OrientationState orientation;
 
   OSVR_ReturnCode ret =
     osvr_GetOrientationState(*m_iface, &timestamp, &orientation);
 
   result.timestamp = timestamp.seconds;
+  result.inputFrameID = mDisplayInfo.mFrameId;
 
   if (ret == OSVR_RETURN_SUCCESS) {
     result.flags |= VRDisplayCapabilityFlags::Cap_Orientation;
     result.orientation[0] = orientation.data[1];
     result.orientation[1] = orientation.data[2];
     result.orientation[2] = orientation.data[3];
     result.orientation[3] = orientation.data[0];
   }
@@ -317,24 +318,24 @@ VRDisplayOSVR::GetSensorState()
     result.position[2] = position.data[2];
   }
 
   return result;
 }
 
 #if defined(XP_WIN)
 
-void
+bool
 VRDisplayOSVR::SubmitFrame(TextureSourceD3D11* aSource,
   const IntSize& aSize,
-  const VRHMDSensorState& aSensorState,
   const gfx::Rect& aLeftEyeRect,
   const gfx::Rect& aRightEyeRect)
 {
   // XXX Add code to submit frame
+  return false;
 }
 
 #endif
 
 void
 VRDisplayOSVR::StartPresentation()
 {
   // XXX Add code to start VR Presentation
@@ -531,17 +532,17 @@ VRSystemManagerOSVR::GetHMDs(nsTArray<Re
   }
 }
 
 bool
 VRSystemManagerOSVR::GetIsPresenting()
 {
   if (mHMDInfo) {
     VRDisplayInfo displayInfo(mHMDInfo->GetDisplayInfo());
-    return displayInfo.GetIsPresenting();
+    return displayInfo.GetPresentingGroups() != kVRGroupNone;
   }
 
   return false;
 }
 
 void
 VRSystemManagerOSVR::HandleInput()
 {
--- a/gfx/vr/gfxVROSVR.h
+++ b/gfx/vr/gfxVROSVR.h
@@ -20,27 +20,26 @@
 
 namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayOSVR : public VRDisplayHost
 {
 public:
-  VRHMDSensorState GetSensorState() override;
   void ZeroSensor() override;
 
 protected:
+  VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
 
 #if defined(XP_WIN)
-  virtual void SubmitFrame(TextureSourceD3D11* aSource,
+  virtual bool SubmitFrame(TextureSourceD3D11* aSource,
     const IntSize& aSize,
-    const VRHMDSensorState& aSensorState,
     const gfx::Rect& aLeftEyeRect,
     const gfx::Rect& aRightEyeRect) override;
 #endif
 
 public:
   explicit VRDisplayOSVR(OSVR_ClientContext* context,
                          OSVR_ClientInterface* iface,
                          OSVR_DisplayConfig* display);
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -433,30 +433,28 @@ VRDisplayOculus::ZeroSensor()
 {
   ovr_RecenterTrackingOrigin(mSession);
   UpdateStageParameters();
 }
 
 VRHMDSensorState
 VRDisplayOculus::GetSensorState()
 {
-  mInputFrameID++;
-
   VRHMDSensorState result;
   double frameDelta = 0.0f;
   if (gfxPrefs::VRPosePredictionEnabled()) {
     // XXX We might need to call ovr_GetPredictedDisplayTime even if we don't use the result.
     // If we don't call it, the Oculus driver will spew out many warnings...
     double predictedFrameTime = ovr_GetPredictedDisplayTime(mSession, 0);
     frameDelta = predictedFrameTime - ovr_GetTimeInSeconds();
   }
   result = GetSensorState(frameDelta);
-  result.inputFrameID = mInputFrameID;
-  mLastSensorState[result.inputFrameID % kMaxLatencyFrames] = result;
+  result.inputFrameID = mDisplayInfo.mFrameId;
   result.position[1] -= mEyeHeight;
+  mDisplayInfo.mLastSensorState[result.inputFrameID % kVRMaxLatencyFrames] = result;
   return result;
 }
 
 VRHMDSensorState
 VRDisplayOculus::GetSensorState(double timeOffset)
 {
   VRHMDSensorState result;
 
@@ -690,36 +688,35 @@ VRDisplayOculus::UpdateConstantBuffers()
 
   ID3D11Buffer *buffer = mVSConstantBuffer;
   mContext->VSSetConstantBuffers(0, 1, &buffer);
   buffer = mPSConstantBuffer;
   mContext->PSSetConstantBuffers(0, 1, &buffer);
   return true;
 }
 
-void
+bool
 VRDisplayOculus::SubmitFrame(TextureSourceD3D11* aSource,
   const IntSize& aSize,
-  const VRHMDSensorState& aSensorState,
   const gfx::Rect& aLeftEyeRect,
   const gfx::Rect& aRightEyeRect)
 {
   if (!mIsPresenting) {
-    return;
+    return false;
   }
   if (mRenderTargets.IsEmpty()) {
     /**
      * 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.
      *       See Bug 1299309.
      **/
-    return;
+    return false;
   }
   MOZ_ASSERT(mDevice);
   MOZ_ASSERT(mContext);
 
   RefPtr<CompositingRenderTargetD3D11> surface = GetNextRenderTarget();
 
   surface->BindRenderTarget(mContext);
 
@@ -770,25 +767,25 @@ VRDisplayOculus::SubmitFrame(TextureSour
   mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &srView);
   // XXX Use Constant from TexSlot in CompositorD3D11.cpp?
 
   ID3D11SamplerState *sampler = mLinearSamplerState;
   mContext->PSSetSamplers(0, 1, &sampler);
 
   if (!UpdateConstantBuffers()) {
     NS_WARNING("Failed to update constant buffers for Oculus");
-    return;
+    return false;
   }
 
   mContext->Draw(4, 0);
 
   ovrResult orv = ovr_CommitTextureSwapChain(mSession, mTextureSet);
   if (orv != ovrSuccess) {
     NS_WARNING("ovr_CommitTextureSwapChain failed.\n");
-    return;
+    return false;
   }
 
   ovrLayerEyeFov layer;
   memset(&layer, 0, sizeof(layer));
   layer.Header.Type = ovrLayerType_EyeFov;
   layer.Header.Flags = 0;
   layer.ColorTexture[0] = mTextureSet;
   layer.ColorTexture[1] = nullptr;
@@ -803,51 +800,62 @@ VRDisplayOculus::SubmitFrame(TextureSour
   layer.Viewport[1].Size.w = aSize.width * aRightEyeRect.width;
   layer.Viewport[1].Size.h = aSize.height * aRightEyeRect.height;
 
   const Point3D& l = mDisplayInfo.mEyeTranslation[0];
   const Point3D& r = mDisplayInfo.mEyeTranslation[1];
   const ovrVector3f hmdToEyeViewOffset[2] = { { l.x, l.y, l.z },
                                               { r.x, r.y, r.z } };
 
+  const VRHMDSensorState& sensorState = mDisplayInfo.GetSensorState();
+
   for (uint32_t i = 0; i < 2; ++i) {
-    Quaternion o(aSensorState.orientation[0],
-      aSensorState.orientation[1],
-      aSensorState.orientation[2],
-      aSensorState.orientation[3]);
+    Quaternion o(sensorState.orientation[0],
+      sensorState.orientation[1],
+      sensorState.orientation[2],
+      sensorState.orientation[3]);
     Point3D vo(hmdToEyeViewOffset[i].x, hmdToEyeViewOffset[i].y, hmdToEyeViewOffset[i].z);
     Point3D p = o.RotatePoint(vo);
     layer.RenderPose[i].Orientation.x = o.x;
     layer.RenderPose[i].Orientation.y = o.y;
     layer.RenderPose[i].Orientation.z = o.z;
     layer.RenderPose[i].Orientation.w = o.w;
-    layer.RenderPose[i].Position.x = p.x + aSensorState.position[0];
-    layer.RenderPose[i].Position.y = p.y + aSensorState.position[1];
-    layer.RenderPose[i].Position.z = p.z + aSensorState.position[2];
+    layer.RenderPose[i].Position.x = p.x + sensorState.position[0];
+    layer.RenderPose[i].Position.y = p.y + sensorState.position[1];
+    layer.RenderPose[i].Position.z = p.z + sensorState.position[2];
   }
 
   ovrLayerHeader *layers = &layer.Header;
-  orv = ovr_SubmitFrame(mSession, aSensorState.inputFrameID, nullptr, &layers, 1);
+  orv = ovr_SubmitFrame(mSession, mDisplayInfo.mFrameId, nullptr, &layers, 1);
+  // ovr_SubmitFrame will fail during the Oculus health and safety warning.
+  // and will start succeeding once the warning has been dismissed by the user.
 
-  if (orv != ovrSuccess) {
-    printf_stderr("ovr_SubmitFrame failed.\n");
+  if (!OVR_UNQUALIFIED_SUCCESS(orv)) {
+    /**
+     * We wish to throttle the framerate for any case that the rendered
+     * result is not visible.  In some cases, such as during the Oculus
+     * "health and safety warning", orv will be > 0 (OVR_SUCCESS but not
+     * OVR_UNQUALIFIED_SUCCESS) and ovr_SubmitFrame will not block.
+     * In this case, returning true would have resulted in an unthrottled
+     * render loop hiting excessive frame rates and consuming resources.
+     */
+    return false;
   }
 
-  // Trigger the next VSync immediately
-  VRManager *vm = VRManager::Get();
-  MOZ_ASSERT(vm);
-  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+  return true;
 }
 
 void
 VRDisplayOculus::NotifyVSync()
 {
   ovrSessionStatus sessionStatus;
   ovrResult ovr = ovr_GetSessionStatus(mSession, &sessionStatus);
   mDisplayInfo.mIsConnected = (ovr == ovrSuccess && sessionStatus.HmdPresent);
+
+  VRDisplayHost::NotifyVSync();
 }
 
 VRControllerOculus::VRControllerOculus(dom::GamepadHand aHand)
   : VRControllerHost(VRDeviceType::Oculus)
   , mIndexTrigger(0.0f)
   , mHandTrigger(0.0f)
   , mVibrateThread(nullptr)
   , mIsVibrateStopped(false)
@@ -1162,17 +1170,17 @@ VRSystemManagerOculus::GetHMDs(nsTArray<
   }
 }
 
 bool
 VRSystemManagerOculus::GetIsPresenting()
 {
   if (mHMDInfo) {
     VRDisplayInfo displayInfo(mHMDInfo->GetDisplayInfo());
-    return displayInfo.GetIsPresenting();
+    return displayInfo.GetPresentingGroups() != 0;
   }
 
   return false;
 }
 
 void
 VRSystemManagerOculus::HandleInput()
 {
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -32,25 +32,24 @@ enum class OculusControllerAxisType : ui
   ThumbstickYAxis,
   NumVRControllerAxisType
 };
 
 class VRDisplayOculus : public VRDisplayHost
 {
 public:
   virtual void NotifyVSync() override;
-  virtual VRHMDSensorState GetSensorState() override;
   void ZeroSensor() override;
 
 protected:
+  virtual VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
-  virtual void SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
+  virtual bool SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
                            const IntSize& aSize,
-                           const VRHMDSensorState& aSensorState,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
   void UpdateStageParameters();
 
 public:
   explicit VRDisplayOculus(ovrSession aSession);
 
 protected:
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -168,22 +168,16 @@ VRDisplayOpenVR::UpdateStageParameters()
 
 void
 VRDisplayOpenVR::ZeroSensor()
 {
   mVRSystem->ResetSeatedZeroPose();
   UpdateStageParameters();
 }
 
-VRHMDSensorState
-VRDisplayOpenVR::GetSensorState()
-{
-  return GetSensorState(0.0f);
-}
-
 void
 VRDisplayOpenVR::PollEvents()
 {
   ::vr::VREvent_t event;
   while (mVRSystem->PollNextEvent(&event, sizeof(event))) {
     if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
       switch (event.eventType) {
       case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
@@ -196,17 +190,17 @@ VRDisplayOpenVR::PollEvents()
         // ignore
         break;
       }
     }
   }
 }
 
 VRHMDSensorState
-VRDisplayOpenVR::GetSensorState(double timeOffset)
+VRDisplayOpenVR::GetSensorState()
 {
   PollEvents();
 
   ::vr::TrackedDevicePose_t poses[::vr::k_unMaxTrackedDeviceCount];
   // Note: We *must* call WaitGetPoses in order for any rendering to happen at all
   mVRCompositor->WaitGetPoses(poses, ::vr::k_unMaxTrackedDeviceCount, nullptr, 0);
 
   VRHMDSensorState result;
@@ -251,16 +245,17 @@ VRDisplayOpenVR::GetSensorState(double t
     result.position[0] = m._41;
     result.position[1] = m._42;
     result.position[2] = m._43;
     result.linearVelocity[0] = pose.vVelocity.v[0];
     result.linearVelocity[1] = pose.vVelocity.v[1];
     result.linearVelocity[2] = pose.vVelocity.v[2];
   }
 
+  result.inputFrameID = mDisplayInfo.mFrameId;
   return result;
 }
 
 void
 VRDisplayOpenVR::StartPresentation()
 {
   if (mIsPresenting) {
     return;
@@ -278,25 +273,24 @@ VRDisplayOpenVR::StopPresentation()
   mVRCompositor->ClearLastSubmittedFrame();
 
   mIsPresenting = false;
 }
 
 
 #if defined(XP_WIN)
 
-void
+bool
 VRDisplayOpenVR::SubmitFrame(TextureSourceD3D11* aSource,
   const IntSize& aSize,
-  const VRHMDSensorState& aSensorState,
   const gfx::Rect& aLeftEyeRect,
   const gfx::Rect& aRightEyeRect)
 {
   if (!mIsPresenting) {
-    return;
+    return false;
   }
 
   ::vr::Texture_t tex;
   tex.handle = (void *)aSource->GetD3D11Texture();
   tex.eType = ::vr::ETextureType::TextureType_DirectX;
   tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto;
 
   ::vr::VRTextureBounds_t bounds;
@@ -318,32 +312,31 @@ VRDisplayOpenVR::SubmitFrame(TextureSour
 
   err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds);
   if (err != ::vr::EVRCompositorError::VRCompositorError_None) {
     printf_stderr("OpenVR Compositor Submit() failed.\n");
   }
 
   mVRCompositor->PostPresentHandoff();
 
-  // Trigger the next VSync immediately
-  VRManager *vm = VRManager::Get();
-  MOZ_ASSERT(vm);
-  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
+  return true;
 }
 
 #endif
 
 void
 VRDisplayOpenVR::NotifyVSync()
 {
   // We update mIsConneced once per frame.
   mDisplayInfo.mIsConnected = ::vr::VR_IsHmdPresent();
 
   // Make sure we respond to OpenVR events even when not presenting
   PollEvents();
+
+  VRDisplayHost::NotifyVSync();
 }
 
 VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aNumButtons,
                                        uint32_t aNumAxes, ::vr::ETrackedDeviceClass aDeviceType)
   : VRControllerHost(VRDeviceType::OpenVR)
   , mTrigger(0)
   , mAxisMove(aNumAxes)
   , mVibrateThread(nullptr)
@@ -590,17 +583,17 @@ VRSystemManagerOpenVR::GetHMDs(nsTArray<
   }
 }
 
 bool
 VRSystemManagerOpenVR::GetIsPresenting()
 {
   if (mOpenVRHMD) {
     VRDisplayInfo displayInfo(mOpenVRHMD->GetDisplayInfo());
-    return displayInfo.GetIsPresenting();
+    return displayInfo.GetPresentingGroups() != kVRGroupNone;
   }
 
   return false;
 }
 
 void
 VRSystemManagerOpenVR::HandleInput()
 {
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -21,41 +21,38 @@
 namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayOpenVR : public VRDisplayHost
 {
 public:
   virtual void NotifyVSync() override;
-  virtual VRHMDSensorState GetSensorState() override;
   void ZeroSensor() override;
 
 protected:
+  virtual VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
 #if defined(XP_WIN)
-  virtual void SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
+  virtual bool SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
                            const IntSize& aSize,
-                           const VRHMDSensorState& aSensorState,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #endif
 
 public:
   explicit VRDisplayOpenVR(::vr::IVRSystem *aVRSystem,
                            ::vr::IVRChaperone *aVRChaperone,
                            ::vr::IVRCompositor *aVRCompositor);
 
 protected:
   virtual ~VRDisplayOpenVR();
   void Destroy();
 
-  VRHMDSensorState GetSensorState(double timeOffset);
-
   // not owned by us; global from OpenVR
   ::vr::IVRSystem *mVRSystem;
   ::vr::IVRChaperone *mVRChaperone;
   ::vr::IVRCompositor *mVRCompositor;
 
   bool mIsPresenting;
 
   void UpdateStageParameters();
--- a/gfx/vr/gfxVRPuppet.cpp
+++ b/gfx/vr/gfxVRPuppet.cpp
@@ -51,17 +51,16 @@ static const uint32_t kPuppetAxes[] = {
 static const uint32_t kNumPuppetAxis = sizeof(kPuppetAxes) /
                                        sizeof(uint32_t);
 
 static const uint32_t kNumPuppetHaptcs = 1;
 
 VRDisplayPuppet::VRDisplayPuppet()
  : VRDisplayHost(VRDeviceType::Puppet)
  , mIsPresenting(false)
- , mFrameNum(0)
 {
   MOZ_COUNT_CTOR_INHERITED(VRDisplayPuppet, VRDisplayHost);
 
   mDisplayInfo.mDisplayName.AssignLiteral("Puppet HMD");
   mDisplayInfo.mIsConnected = true;
   mDisplayInfo.mIsMounted = false;
   mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
                                   VRDisplayCapabilityFlags::Cap_Orientation |
@@ -149,22 +148,17 @@ VRDisplayPuppet::Destroy()
 void
 VRDisplayPuppet::ZeroSensor()
 {
 }
 
 VRHMDSensorState
 VRDisplayPuppet::GetSensorState()
 {
-  return GetSensorState(0.0f);
-}
-
-VRHMDSensorState
-VRDisplayPuppet::GetSensorState(double timeOffset)
-{
+  mSensorState.inputFrameID = mDisplayInfo.mFrameId;
   return mSensorState;
 }
 
 void
 VRDisplayPuppet::SetSensorState(const VRHMDSensorState& aSensorState)
 {
   memcpy(&mSensorState, &aSensorState, sizeof(mSensorState));
 }
@@ -290,25 +284,24 @@ VRDisplayPuppet::UpdateConstantBuffers()
 
   ID3D11Buffer *buffer = mVSConstantBuffer;
   mContext->VSSetConstantBuffers(0, 1, &buffer);
   buffer = mPSConstantBuffer;
   mContext->PSSetConstantBuffers(0, 1, &buffer);
   return true;
 }
 
-void
+bool
 VRDisplayPuppet::SubmitFrame(TextureSourceD3D11* aSource,
                              const IntSize& aSize,
-                             const VRHMDSensorState& aSensorState,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
   if (!mIsPresenting) {
-    return;
+    return false;
   }
 
   VRManager *vm = VRManager::Get();
   MOZ_ASSERT(vm);
 
   switch (gfxPrefs::VRPuppetSubmitFrame()) {
     case 0:
       // The VR frame is not displayed.
@@ -344,49 +337,48 @@ VRDisplayPuppet::SubmitFrame(TextureSour
           desc2.BindFlags = 0;
           desc2.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
           desc2.MiscFlags = 0;
 
           ID3D11Texture2D* stagingTexture = nullptr;
           hr = mDevice->CreateTexture2D(&desc2, nullptr, &stagingTexture);
           if (FAILED(hr)) {
             MOZ_ASSERT(false, "Failed to create a staging texture");
-            return;
+            return false;
           }
           // Copy the texture to a staging resource
           mContext->CopyResource(stagingTexture, texture);
           // Map the staging resource
           hr = mContext->Map(stagingTexture,
                              0,  // Subsource
                              D3D11_MAP_READ,
                              0,  // MapFlags
                              &mapInfo);
           if (FAILED(hr)) {
             MOZ_ASSERT(false, "Failed to map staging texture");
           }
           mappedTexture = stagingTexture;
-        }
-        else {
+        } else {
           MOZ_ASSERT(false, "Failed to map staging texture");
-          return;
+          return false;
         }
       } else {
         mappedTexture = texture;
       }
       // Ideally, we should convert the srcData to a PNG image and decode it
       // to a Base64 string here, but the GPU process does not have the privilege to
       // access the image library. So, we have to convert the RAW image data
       // to a base64 string and forward it to let the content process to
       // do the image conversion.
       char* srcData = static_cast<char*>(mapInfo.pData);
       VRSubmitFrameResultInfo result;
       result.mFormat = SurfaceFormat::B8G8R8A8;
       result.mWidth = desc.Width;
       result.mHeight = desc.Height;
-      result.mFrameNum = mFrameNum;
+      result.mFrameNum = mDisplayInfo.mFrameId;
       nsCString rawString(Substring((char*)srcData, mapInfo.RowPitch * desc.Height));
 
       if (Base64Encode(rawString, result.mBase64Image) != NS_OK) {
         MOZ_ASSERT(false, "Failed to encode base64 images.");
       }
       mContext->Unmap(mappedTexture, 0);
       // Dispatch the base64 encoded string to the DOM side, and it will be decoded
       // and convert to a PNG image there.
@@ -445,55 +437,52 @@ VRDisplayPuppet::SubmitFrame(TextureSour
       mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &srView);
       // XXX Use Constant from TexSlot in CompositorD3D11.cpp?
 
       ID3D11SamplerState *sampler = mLinearSamplerState;
       mContext->PSSetSamplers(0, 1, &sampler);
 
       if (!UpdateConstantBuffers()) {
         NS_WARNING("Failed to update constant buffers for Puppet");
-        return;
+        return false;
       }
       mContext->Draw(4, 0);
       break;
     }
   }
 
-  // Trigger the next VSync immediately
-  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
-  ++mFrameNum;
+  // We will always return false for gfxVRPuppet to ensure that the fallback "watchdog"
+  // code in VRDisplayHost::NotifyVSync() throttles the render loop.
+  return false;
 }
 #else
-void
+bool
 VRDisplayPuppet::SubmitFrame(TextureSourceOGL* aSource,
                              const IntSize& aSize,
-                             const VRHMDSensorState& aSensorState,
                              const gfx::Rect& aLeftEyeRect,
                              const gfx::Rect& aRightEyeRect)
 {
   if (!mIsPresenting) {
-    return;
+    return false;
   }
 
   // TODO: Bug 1343730, Need to block until the next simulated
   // vblank interval and capture frames for use in reftests.
 
-  // Trigger the next VSync immediately
-  VRManager *vm = VRManager::Get();
-  MOZ_ASSERT(vm);
-  vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
-  ++mFrameNum;
+  return false;
 }
 #endif
 
 void
 VRDisplayPuppet::NotifyVSync()
 {
   // We update mIsConneced once per frame.
   mDisplayInfo.mIsConnected = true;
+
+  VRDisplayHost::NotifyVSync();
 }
 
 VRControllerPuppet::VRControllerPuppet(dom::GamepadHand aHand)
   : VRControllerHost(VRDeviceType::Puppet)
   , mButtonPressState(0)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerPuppet, VRControllerHost);
   mControllerInfo.mControllerName.AssignLiteral("Puppet Gamepad");
@@ -630,17 +619,17 @@ VRSystemManagerPuppet::GetHMDs(nsTArray<
   aHMDResult.AppendElement(mPuppetHMD);
 }
 
 bool
 VRSystemManagerPuppet::GetIsPresenting()
 {
   if (mPuppetHMD) {
     VRDisplayInfo displayInfo(mPuppetHMD->GetDisplayInfo());
-    return displayInfo.GetIsPresenting();
+    return displayInfo.GetPresentingGroups() != kVRGroupNone;
   }
 
   return false;
 }
 
 void
 VRSystemManagerPuppet::HandleInput()
 {
--- a/gfx/vr/gfxVRPuppet.h
+++ b/gfx/vr/gfxVRPuppet.h
@@ -15,46 +15,42 @@ namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayPuppet : public VRDisplayHost
 {
 public:
   void SetDisplayInfo(const VRDisplayInfo& aDisplayInfo);
   virtual void NotifyVSync() override;
-  virtual VRHMDSensorState GetSensorState() 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)
-  virtual void SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
+  virtual bool SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
                            const IntSize& aSize,
-                           const VRHMDSensorState& aSensorState,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) override;
 #else
-  virtual void SubmitFrame(mozilla::layers::TextureSourceOGL* aSource,
+  virtual bool SubmitFrame(mozilla::layers::TextureSourceOGL* aSource,
                            const IntSize& aSize,
-                           const VRHMDSensorState& aSensorState,
                            const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect);
 #endif // XP_WIN
 
 public:
   explicit VRDisplayPuppet();
 
 protected:
   virtual ~VRDisplayPuppet();
   void Destroy();
 
-  VRHMDSensorState GetSensorState(double timeOffset);
-
   bool mIsPresenting;
 
 private:
 #if defined(XP_WIN)
   bool UpdateConstantBuffers();
 
   RefPtr<ID3D11Device> mDevice;
   RefPtr<ID3D11DeviceContext> mContext;
@@ -65,17 +61,16 @@ private:
   layers::PixelShaderConstants mPSConstants;
   RefPtr<ID3D11Buffer> mVSConstantBuffer;
   RefPtr<ID3D11Buffer> mPSConstantBuffer;
   RefPtr<ID3D11Buffer> mVertexBuffer;
   RefPtr<ID3D11InputLayout> mInputLayout;
 #endif
 
   VRHMDSensorState mSensorState;
-  uint32_t mFrameNum;
 };
 
 class VRControllerPuppet : public VRControllerHost
 {
 public:
   explicit VRControllerPuppet(dom::GamepadHand aHand);
   void SetButtonPressState(uint32_t aButton, bool aPressed);
   uint64_t GetButtonPressState();
--- a/gfx/vr/ipc/PVRManager.ipdl
+++ b/gfx/vr/ipc/PVRManager.ipdl
@@ -35,27 +35,30 @@ sync protocol PVRManager
 {
   manages PTexture;
   manages PVRLayer;
 
 parent:
   async PTexture(SurfaceDescriptor aSharedData, LayersBackend aBackend,
                  TextureFlags aTextureFlags, uint64_t aSerial);
 
-  async PVRLayer(uint32_t aDisplayID, float aLeftEyeX, float aLeftEyeY, float aLeftEyeWidth, float aLeftEyeHeight, float aRightEyeX, float aRightEyeY, float aRightEyeWidth, float aRightEyeHeight);
+  async PVRLayer(uint32_t aDisplayID, float aLeftEyeX, float aLeftEyeY,
+                 float aLeftEyeWidth, float aLeftEyeHeight, float aRightEyeX,
+                 float aRightEyeY, float aRightEyeWidth, float aRightEyeHeight,
+                 uint32_t aGroup);
 
   // (Re)Enumerate VR Displays.  An updated list of VR displays will be returned
   // asynchronously to children via UpdateDisplayInfo.
   async RefreshDisplays();
 
   // Reset the sensor of the display identified by aDisplayID so that the current
   // sensor state is the "Zero" position.
   async ResetSensor(uint32_t aDisplayID);
 
-  sync GetSensorState(uint32_t aDisplayID) returns(VRHMDSensorState aState);
+  async SetGroupMask(uint32_t aDisplayID, uint32_t aGroupMask);
   async SetHaveEventListener(bool aHaveEventListener);
 
   async ControllerListenerAdded();
   async ControllerListenerRemoved();
   async VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                       double aIntensity, double aDuration, uint32_t aPromiseID);
   async StopVibrateHaptic(uint32_t aControllerIdx);
 
@@ -76,18 +79,16 @@ child:
   async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
   // Notify children of updated VR display enumeration and details.  This will
   // be sent to all children when the parent receives RefreshDisplays, even
   // if no changes have been detected.  This ensures that Promises exposed
   // through DOM calls are always resolved.
   async UpdateDisplayInfo(VRDisplayInfo[] aDisplayUpdates);
 
-  async NotifyVSync();
-  async NotifyVRVSync(uint32_t aDisplayID);
   async DispatchSubmitFrameResult(uint32_t aDisplayID, VRSubmitFrameResultInfo aResult);
   async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
   async ReplyGamepadVibrateHaptic(uint32_t aPromiseID);
 
   async ReplyCreateVRServiceTestDisplay(nsCString aID, uint32_t aPromiseID,
                                         uint32_t aDeviceID);
   async ReplyCreateVRServiceTestController(nsCString aID, uint32_t aPromiseID,
                                            uint32_t aDeviceID);
--- a/gfx/vr/ipc/VRLayerParent.cpp
+++ b/gfx/vr/ipc/VRLayerParent.cpp
@@ -5,21 +5,22 @@
 
 
 #include "VRLayerParent.h"
 #include "mozilla/Unused.h"
 
 namespace mozilla {
 namespace gfx {
 
-VRLayerParent::VRLayerParent(uint32_t aVRDisplayID, const Rect& aLeftEyeRect, const Rect& aRightEyeRect)
+VRLayerParent::VRLayerParent(uint32_t aVRDisplayID, const Rect& aLeftEyeRect, const Rect& aRightEyeRect, const uint32_t aGroup)
   : mIPCOpen(true)
   , mVRDisplayID(aVRDisplayID)
   , mLeftEyeRect(aLeftEyeRect)
   , mRightEyeRect(aRightEyeRect)
+  , mGroup(aGroup)
 {
 }
 
 VRLayerParent::~VRLayerParent()
 {
   MOZ_COUNT_DTOR(VRLayerParent);
 }
 
--- a/gfx/vr/ipc/VRLayerParent.h
+++ b/gfx/vr/ipc/VRLayerParent.h
@@ -14,30 +14,33 @@
 
 namespace mozilla {
 namespace gfx {
 
 class VRLayerParent : public PVRLayerParent {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRLayerParent)
 
 public:
-  VRLayerParent(uint32_t aVRDisplayID, const Rect& aLeftEyeRect, const Rect& aRightEyeRect);
+  VRLayerParent(uint32_t aVRDisplayID, const Rect& aLeftEyeRect,
+                const Rect& aRightEyeRect, const uint32_t aGroup);
   virtual mozilla::ipc::IPCResult RecvSubmitFrame(PTextureParent* texture) override;
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
   uint32_t GetDisplayID() const { return mVRDisplayID; }
+  uint32_t GetGroup() const { return mGroup; }
 protected:
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
   virtual ~VRLayerParent();
   void Destroy();
 
   bool mIPCOpen;
 
   uint32_t mVRDisplayID;
   gfx::IntSize mSize;
   gfx::Rect mLeftEyeRect;
   gfx::Rect mRightEyeRect;
+  uint32_t mGroup;
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif
--- a/gfx/vr/ipc/VRManagerChild.cpp
+++ b/gfx/vr/ipc/VRManagerChild.cpp
@@ -35,17 +35,16 @@ static StaticRefPtr<VRManagerParent> sVR
 
 void ReleaseVRManagerParentSingleton() {
   sVRManagerParentSingleton = nullptr;
 }
 
 VRManagerChild::VRManagerChild()
   : TextureForwarder()
   , mDisplaysInitialized(false)
-  , mInputFrameID(-1)
   , mMessageLoop(MessageLoop::current())
   , mFrameRequestCallbackCounter(0)
   , mBackend(layers::LayersBackend::LAYERS_NONE)
   , mPromiseID(0)
   , mVRMockDisplay(nullptr)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -185,17 +184,18 @@ PVRLayerChild*
 VRManagerChild::AllocPVRLayerChild(const uint32_t& aDisplayID,
                                    const float& aLeftEyeX,
                                    const float& aLeftEyeY,
                                    const float& aLeftEyeWidth,
                                    const float& aLeftEyeHeight,
                                    const float& aRightEyeX,
                                    const float& aRightEyeY,
                                    const float& aRightEyeWidth,
-                                   const float& aRightEyeHeight)
+                                   const float& aRightEyeHeight,
+                                   const uint32_t& aGroup)
 {
   RefPtr<VRLayerChild> layer = new VRLayerChild(aDisplayID, this);
   return layer.forget().take();
 }
 
 bool
 VRManagerChild::DeallocPVRLayerChild(PVRLayerChild* actor)
 {
@@ -317,22 +317,16 @@ VRManagerChild::CreateVRServiceTestDispl
 void
 VRManagerChild::CreateVRServiceTestController(const nsCString& aID, dom::Promise* aPromise)
 {
   SendCreateVRServiceTestController(aID, mPromiseID);
   mPromiseList.Put(mPromiseID, aPromise);
   ++mPromiseID;
 }
 
-int
-VRManagerChild::GetInputFrameID()
-{
-  return mInputFrameID;
-}
-
 mozilla::ipc::IPCResult
 VRManagerChild::RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages)
 {
   for (InfallibleTArray<AsyncParentMessageData>::index_type i = 0; i < aMessages.Length(); ++i) {
     const AsyncParentMessageData& message = aMessages[i];
 
     switch (message.type()) {
       case AsyncParentMessageData::TOpNotifyNotUsed: {
@@ -400,33 +394,36 @@ VRManagerChild::DeallocShmem(ipc::Shmem&
 {
   return PVRManagerChild::DeallocShmem(aShmem);
 }
 
 PVRLayerChild*
 VRManagerChild::CreateVRLayer(uint32_t aDisplayID,
                               const Rect& aLeftEyeRect,
                               const Rect& aRightEyeRect,
-                              nsIEventTarget* aTarget)
+                              nsIEventTarget* aTarget,
+                              uint32_t aGroup)
 {
   PVRLayerChild* vrLayerChild = AllocPVRLayerChild(aDisplayID, aLeftEyeRect.x,
                                                    aLeftEyeRect.y, aLeftEyeRect.width,
                                                    aLeftEyeRect.height, aRightEyeRect.x,
                                                    aRightEyeRect.y, aRightEyeRect.width,
-                                                   aRightEyeRect.height);
+                                                   aRightEyeRect.height,
+                                                   aGroup);
   // Do the DOM labeling.
   if (aTarget) {
     SetEventTargetForActor(vrLayerChild, aTarget);
     MOZ_ASSERT(vrLayerChild->GetActorEventTarget());
   }
   return SendPVRLayerConstructor(vrLayerChild, aDisplayID, aLeftEyeRect.x,
                                  aLeftEyeRect.y, aLeftEyeRect.width,
                                  aLeftEyeRect.height, aRightEyeRect.x,
                                  aRightEyeRect.y, aRightEyeRect.width,
-                                 aRightEyeRect.height);
+                                 aRightEyeRect.height,
+                                 aGroup);
 }
 
 
 // XXX TODO - VRManagerChild::FrameRequest is the same as nsIDocument::FrameRequest, should we consolodate these?
 struct VRManagerChild::FrameRequest
 {
   FrameRequest(mozilla::dom::FrameRequestCallback& aCallback,
     int32_t aHandle) :
@@ -474,38 +471,16 @@ VRManagerChild::ScheduleFrameRequestCall
 void
 VRManagerChild::CancelFrameRequestCallback(int32_t aHandle)
 {
   // mFrameRequestCallbacks is stored sorted by handle
   mFrameRequestCallbacks.RemoveElementSorted(aHandle);
 }
 
 mozilla::ipc::IPCResult
-VRManagerChild::RecvNotifyVSync()
-{
-  for (auto& display : mDisplays) {
-    display->NotifyVsync();
-  }
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
-VRManagerChild::RecvNotifyVRVSync(const uint32_t& aDisplayID)
-{
-  for (auto& display : mDisplays) {
-    if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) {
-      display->NotifyVRVsync();
-    }
-  }
-
-  return IPC_OK();
-}
-
-mozilla::ipc::IPCResult
 VRManagerChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
 {
   // VRManagerChild could be at other processes, but GamepadManager
   // only exists at the content process or the same process
   // in non-e10s mode.
   MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess());
 
   RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
--- a/gfx/vr/ipc/VRManagerChild.h
+++ b/gfx/vr/ipc/VRManagerChild.h
@@ -43,17 +43,16 @@ public:
 
   static VRManagerChild* Get();
 
   // Indicate that an observer wants to receive VR events.
   void AddListener(dom::VREventObserver* aObserver);
   // Indicate that an observer should no longer receive VR events.
   void RemoveListener(dom::VREventObserver* aObserver);
 
-  int GetInputFrameID();
   bool GetVRDisplays(nsTArray<RefPtr<VRDisplayClient> >& aDisplays);
   bool RefreshVRDisplaysWithCallback(uint64_t aWindowId);
   void AddPromise(const uint32_t& aID, dom::Promise* aPromise);
 
   void CreateVRServiceTestDisplay(const nsCString& aID, dom::Promise* aPromise);
   void CreateVRServiceTestController(const nsCString& aID, dom::Promise* aPromise);
 
   static void InitSameProcess();
@@ -71,17 +70,18 @@ public:
     uint64_t aSerial,
     wr::MaybeExternalImageId& aExternalImageId,
     nsIEventTarget* aTarget = nullptr) override;
   virtual void CancelWaitForRecycle(uint64_t aTextureId) override;
 
   PVRLayerChild* CreateVRLayer(uint32_t aDisplayID,
                                const Rect& aLeftEyeRect,
                                const Rect& aRightEyeRect,
-                               nsIEventTarget* aTarget);
+                               nsIEventTarget* aTarget,
+                               uint32_t aGroup);
 
   static void IdentifyTextureHost(const layers::TextureFactoryIdentifier& aIdentifier);
   layers::LayersBackend GetBackendType() const;
   layers::SyncObject* GetSyncObject() { return mSyncObject; }
 
   virtual MessageLoop* GetMessageLoop() const override { return mMessageLoop; }
   virtual base::ProcessId GetParentPid() const override { return OtherPid(); }
 
@@ -114,25 +114,24 @@ protected:
   virtual PVRLayerChild* AllocPVRLayerChild(const uint32_t& aDisplayID,
                                             const float& aLeftEyeX,
                                             const float& aLeftEyeY,
                                             const float& aLeftEyeWidth,
                                             const float& aLeftEyeHeight,
                                             const float& aRightEyeX,
                                             const float& aRightEyeY,
                                             const float& aRightEyeWidth,
-                                            const float& aRightEyeHeight) override;
+                                            const float& aRightEyeHeight,
+                                            const uint32_t& aGroup) override;
   virtual bool DeallocPVRLayerChild(PVRLayerChild* actor) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateDisplayInfo(nsTArray<VRDisplayInfo>&& aDisplayUpdates) override;
 
   virtual mozilla::ipc::IPCResult RecvParentAsyncMessages(InfallibleTArray<AsyncParentMessageData>&& aMessages) override;
 
-  virtual mozilla::ipc::IPCResult RecvNotifyVSync() override;
-  virtual mozilla::ipc::IPCResult RecvNotifyVRVSync(const uint32_t& aDisplayID) override;
   virtual mozilla::ipc::IPCResult RecvDispatchSubmitFrameResult(const uint32_t& aDisplayID, const VRSubmitFrameResultInfo& aResult) override;
   virtual mozilla::ipc::IPCResult RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
   virtual mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic(const uint32_t& aPromiseID) override;
 
   virtual mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestDisplay(const nsCString& aID,
                                                                       const uint32_t& aPromiseID,
                                                                       const uint32_t& aDeviceID) override;
   virtual mozilla::ipc::IPCResult RecvReplyCreateVRServiceTestController(const nsCString& aID,
@@ -170,18 +169,16 @@ private:
   * make sure if there is no newer usage.
   */
   void NotifyNotUsed(uint64_t aTextureId, uint64_t aFwdTransactionId);
 
   nsTArray<RefPtr<VRDisplayClient> > mDisplays;
   bool mDisplaysInitialized;
   nsTArray<uint64_t> mNavigatorCallbacks;
 
-  int32_t mInputFrameID;
-
   MessageLoop* mMessageLoop;
 
   struct FrameRequest;
 
   nsTArray<FrameRequest> mFrameRequestCallbacks;
   /**
   * The current frame request callback handle
   */
--- a/gfx/vr/ipc/VRManagerParent.cpp
+++ b/gfx/vr/ipc/VRManagerParent.cpp
@@ -60,22 +60,24 @@ PVRLayerParent*
 VRManagerParent::AllocPVRLayerParent(const uint32_t& aDisplayID,
                                      const float& aLeftEyeX,
                                      const float& aLeftEyeY,
                                      const float& aLeftEyeWidth,
                                      const float& aLeftEyeHeight,
                                      const float& aRightEyeX,
                                      const float& aRightEyeY,
                                      const float& aRightEyeWidth,
-                                     const float& aRightEyeHeight)
+                                     const float& aRightEyeHeight,
+                                     const uint32_t& aGroup)
 {
   RefPtr<VRLayerParent> layer;
   layer = new VRLayerParent(aDisplayID,
                             Rect(aLeftEyeX, aLeftEyeY, aLeftEyeWidth, aLeftEyeHeight),
-                            Rect(aRightEyeX, aRightEyeY, aRightEyeWidth, aRightEyeHeight));
+                            Rect(aRightEyeX, aRightEyeY, aRightEyeWidth, aRightEyeHeight),
+                            aGroup);
   VRManager* vm = VRManager::Get();
   RefPtr<gfx::VRDisplayHost> display = vm->GetDisplay(aDisplayID);
   if (display) {
     display->AddLayer(layer);
   }
   return layer.forget().take();
 }
 
@@ -249,22 +251,22 @@ VRManagerParent::RecvResetSensor(const u
   if (display != nullptr) {
     display->ZeroSensor();
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-VRManagerParent::RecvGetSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState)
+VRManagerParent::RecvSetGroupMask(const uint32_t& aDisplayID, const uint32_t& aGroupMask)
 {
   VRManager* vm = VRManager::Get();
   RefPtr<gfx::VRDisplayHost> display = vm->GetDisplay(aDisplayID);
   if (display != nullptr) {
-    *aState = display->GetSensorState();
+    display->SetGroupMask(aGroupMask);
   }
   return IPC_OK();
 }
 
 bool
 VRManagerParent::HaveEventListener()
 {
   return mHaveEventListener;
--- a/gfx/vr/ipc/VRManagerParent.h
+++ b/gfx/vr/ipc/VRManagerParent.h
@@ -75,25 +75,26 @@ protected:
   virtual PVRLayerParent* AllocPVRLayerParent(const uint32_t& aDisplayID,
                                               const float& aLeftEyeX,
                                               const float& aLeftEyeY,
                                               const float& aLeftEyeWidth,
                                               const float& aLeftEyeHeight,
                                               const float& aRightEyeX,
                                               const float& aRightEyeY,
                                               const float& aRightEyeWidth,
-                                              const float& aRightEyeHeight) override;
+                                              const float& aRightEyeHeight,
+                                              const uint32_t& aGroup) override;
   virtual bool DeallocPVRLayerParent(PVRLayerParent* actor) override;
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
   void OnChannelConnected(int32_t pid) override;
 
   virtual mozilla::ipc::IPCResult RecvRefreshDisplays() override;
   virtual mozilla::ipc::IPCResult RecvResetSensor(const uint32_t& aDisplayID) override;
-  virtual mozilla::ipc::IPCResult RecvGetSensorState(const uint32_t& aDisplayID, VRHMDSensorState* aState) override;
+  virtual mozilla::ipc::IPCResult RecvSetGroupMask(const uint32_t& aDisplayID, const uint32_t& aGroupMask) override;
   virtual mozilla::ipc::IPCResult RecvSetHaveEventListener(const bool& aHaveEventListener) override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerAdded() override;
   virtual mozilla::ipc::IPCResult RecvControllerListenerRemoved() override;
   virtual mozilla::ipc::IPCResult RecvVibrateHaptic(const uint32_t& aControllerIdx, const uint32_t& aHapticIndex,
                                                     const double& aIntensity, const double& aDuration, const uint32_t& aPromiseID) override;
   virtual mozilla::ipc::IPCResult RecvStopVibrateHaptic(const uint32_t& aControllerIdx) override;
   
   virtual mozilla::ipc::IPCResult RecvCreateVRTestSystem() override;
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -35,45 +35,57 @@ struct ParamTraits<mozilla::gfx::VRDispl
   {
     WriteParam(aMsg, aParam.mType);
     WriteParam(aMsg, aParam.mDisplayID);
     WriteParam(aMsg, aParam.mDisplayName);
     WriteParam(aMsg, aParam.mCapabilityFlags);
     WriteParam(aMsg, aParam.mEyeResolution);
     WriteParam(aMsg, aParam.mIsConnected);
     WriteParam(aMsg, aParam.mIsMounted);
-    WriteParam(aMsg, aParam.mIsPresenting);
+    WriteParam(aMsg, aParam.mPresentingGroups);
+    WriteParam(aMsg, aParam.mGroupMask);
     WriteParam(aMsg, aParam.mStageSize);
     WriteParam(aMsg, aParam.mSittingToStandingTransform);
+    WriteParam(aMsg, aParam.mFrameId);
     for (int i = 0; i < mozilla::gfx::VRDisplayInfo::NumEyes; i++) {
       WriteParam(aMsg, aParam.mEyeFOV[i]);
       WriteParam(aMsg, aParam.mEyeTranslation[i]);
     }
+    for (int i = 0; i < mozilla::gfx::kVRMaxLatencyFrames; i++) {
+      WriteParam(aMsg, aParam.mLastSensorState[i]);
+    }
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     if (!ReadParam(aMsg, aIter, &(aResult->mType)) ||
         !ReadParam(aMsg, aIter, &(aResult->mDisplayID)) ||
         !ReadParam(aMsg, aIter, &(aResult->mDisplayName)) ||
         !ReadParam(aMsg, aIter, &(aResult->mCapabilityFlags)) ||
         !ReadParam(aMsg, aIter, &(aResult->mEyeResolution)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsConnected)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsMounted)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mIsPresenting)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mPresentingGroups)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mGroupMask)) ||
         !ReadParam(aMsg, aIter, &(aResult->mStageSize)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mSittingToStandingTransform))) {
+        !ReadParam(aMsg, aIter, &(aResult->mSittingToStandingTransform)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mFrameId))) {
       return false;
     }
     for (int i = 0; i < mozilla::gfx::VRDisplayInfo::NumEyes; i++) {
       if (!ReadParam(aMsg, aIter, &(aResult->mEyeFOV[i])) ||
           !ReadParam(aMsg, aIter, &(aResult->mEyeTranslation[i]))) {
         return false;
       }
     }
+    for (int i = 0; i < mozilla::gfx::kVRMaxLatencyFrames; i++) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mLastSensorState[i]))) {
+        return false;
+      }
+    }
 
     return true;
   }
 };
 
 template <>
 struct ParamTraits<mozilla::gfx::VRHMDSensorState>
 {
--- a/gfx/vr/ovr_capi_dynamic.h
+++ b/gfx/vr/ovr_capi_dynamic.h
@@ -718,14 +718,26 @@ typedef ovrResult (OVR_PFN* pfn_ovr_Crea
 
 typedef ovrResult (OVR_PFN* pfn_ovr_GetMirrorTextureBufferGL)(ovrSession session,
 	ovrMirrorTexture mirrorTexture,
 	unsigned int* out_TexId);
 
 #define OVR_KEY_EYE_HEIGHT "EyeHeight" // float meters
 #define OVR_DEFAULT_EYE_HEIGHT 1.675f
 
+#if !defined(OVR_SUCCESS)
+#define OVR_SUCCESS(result) (result >= 0)
+#endif
+
+#if !defined(OVR_UNQUALIFIED_SUCCESS)
+#define OVR_UNQUALIFIED_SUCCESS(result) (result == ovrSuccess)
+#endif
+
+#if !defined(OVR_FAILURE)
+#define OVR_FAILURE(result) (!OVR_SUCCESS(result))
+#endif
+
 #ifdef __cplusplus
 }
 #endif
 
 #endif /* mozilla_ovr_capi_dynamic_h_ */
 #endif /* OVR_CAPI_h */
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -1041,18 +1041,16 @@ description =
 [PWebRenderBridge::UpdateImage]
 description =
 [PWebRenderBridge::DeleteImage]
 description =
 [PWebRenderBridge::DPSyncEnd]
 description =
 [PWebRenderBridge::DPGetSnapshot]
 description =
-[PVRManager::GetSensorState]
-description =
 [PHal::GetCurrentBatteryInformation]
 description =
 [PHal::GetCurrentNetworkInformation]
 description =
 [PHal::GetScreenEnabled]
 description =
 [PHal::GetKeyLightEnabled]
 description =