Bug 1242967 - VR HMDs should create their own VsyncSource, and notify widget when necessary draft
authorVladimir Vukicevic <vladimir@pobox.com>
Thu, 01 Oct 2015 14:24:53 -0400
changeset 326263 50563de7e632dd12455bf58426749e0e317705f8
parent 326250 ca523680ed6b44b1e259f6f93cdd13fc6b0ab1db
child 513577 0542a28a945206903b10ea2fc17820dbd66bbc16
push id10122
push uservladimir@pobox.com
push dateWed, 27 Jan 2016 19:09:44 +0000
bugs1242967
milestone47.0a1
Bug 1242967 - VR HMDs should create their own VsyncSource, and notify widget when necessary
browser/base/content/browser-fullScreen.js
browser/base/content/tab-content.js
dom/base/Element.cpp
dom/base/nsDOMWindowUtils.cpp
dom/base/nsDocument.cpp
dom/base/nsDocument.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsIDocument.h
dom/base/nsPIDOMWindow.h
dom/interfaces/base/nsIDOMWindowUtils.idl
gfx/thebes/gfxVsync.cpp
gfx/vr/VRDeviceProxy.cpp
gfx/vr/VRDeviceProxy.h
gfx/vr/gfxVR.cpp
gfx/vr/gfxVR.h
gfx/vr/gfxVRCardboard.cpp
gfx/vr/gfxVROculus.cpp
gfx/vr/gfxVROculus.h
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/generic/nsFrame.cpp
widget/nsBaseWidget.cpp
widget/nsBaseWidget.h
widget/nsIWidget.h
--- a/browser/base/content/browser-fullScreen.js
+++ b/browser/base/content/browser-fullScreen.js
@@ -152,19 +152,24 @@ var FullScreen = {
       case "MozDOMFullscreen:Exited":
         this.cleanupDomFullscreen();
         break;
     }
   },
 
   receiveMessage: function(aMessage) {
     let browser = aMessage.target;
+    let data = aMessage.data;
     switch (aMessage.name) {
       case "DOMFullscreen:Request": {
-        this._windowUtils.remoteFrameFullscreenChanged(browser);
+        let hmd = 0;
+        if (data && data.vrDeviceID) {
+          hmd = aMessage.data.vrDeviceID;
+        }
+        this._windowUtils.remoteFrameFullscreenChanged(browser, hmd);
         break;
       }
       case "DOMFullscreen:NewOrigin": {
         this._WarningBox.show(aMessage.data.originNoSuffix);
         break;
       }
       case "DOMFullscreen:Exit": {
         this._windowUtils.remoteFrameFullscreenReverted();
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -643,17 +643,21 @@ var DOMFullscreenHandler = {
         break;
       }
     }
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "MozDOMFullscreen:Request": {
-        sendAsyncMessage("DOMFullscreen:Request");
+        let data = {
+          // will be 0 if not VR
+          vrDeviceID: this._windowUtils.vrHMDDeviceID
+        };
+        sendAsyncMessage("DOMFullscreen:Request", data);
         break;
       }
       case "MozDOMFullscreen:NewOrigin": {
         this._fullscreenDoc = aEvent.target;
         sendAsyncMessage("DOMFullscreen:NewOrigin", {
           originNoSuffix: this._fullscreenDoc.nodePrincipal.originNoSuffix,
         });
         break;
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3290,17 +3290,17 @@ Element::MozRequestFullScreen(JSContext*
 
     if (convertible) {
       if (!fsOptions.Init(aCx, aOptions)) {
         aError.Throw(NS_ERROR_FAILURE);
         return;
       }
 
       if (fsOptions.mVrDisplay) {
-        request->mVRHMDDevice = fsOptions.mVrDisplay->GetHMD();
+        request->mVRHMDDeviceIndex = fsOptions.mVrDisplay->GetHMD()->GetDeviceInfo().GetDeviceID();
       }
     }
   }
 
   OwnerDoc()->AsyncRequestFullScreen(Move(request));
 }
 
 void
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3029,22 +3029,23 @@ nsDOMWindowUtils::SetScrollPositionClamp
   }
 
   nsLayoutUtils::SetScrollPositionClampingScrollPortSize(presShell, CSSSize(aWidth, aHeight));
 
   return NS_OK;
 }
 
 nsresult
-nsDOMWindowUtils::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement)
+nsDOMWindowUtils::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
+                                               PRInt32 aVRDeviceIndex)
 {
   nsCOMPtr<nsIDocument> doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
-  doc->RemoteFrameFullscreenChanged(aFrameElement);
+  doc->RemoteFrameFullscreenChanged(aFrameElement, aVRDeviceIndex);
   return NS_OK;
 }
 
 nsresult
 nsDOMWindowUtils::RemoteFrameFullscreenReverted()
 {
   nsCOMPtr<nsIDocument> doc = GetDocument();
   NS_ENSURE_STATE(doc);
@@ -3861,16 +3862,30 @@ nsDOMWindowUtils::SetNextPaintSyncId(int
       return NS_OK;
     }
   }
 
   NS_WARNING("Paint sync id could not be set on the ClientLayerManager");
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMWindowUtils::GetVrHMDDeviceID(uint32_t* aResult)
+{
+  if (!nsContentUtils::IsCallerChrome()) {
+    return NS_ERROR_DOM_SECURITY_ERR;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  NS_ENSURE_STATE(window);
+
+  *aResult = window->GetVRHMDDeviceIndex();
+  return NS_OK;
+}
+
 NS_INTERFACE_MAP_BEGIN(nsTranslationNodeList)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsITranslationNodeList)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_ADDREF(nsTranslationNodeList)
 NS_IMPL_RELEASE(nsTranslationNodeList)
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -235,18 +235,18 @@
 #include "nsIStructuredCloneContainer.h"
 #include "nsIMutableArray.h"
 #include "nsContentPermissionHelper.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "nsWindowMemoryReporter.h"
 #include "nsLocation.h"
 #include "mozilla/dom/FontFaceSet.h"
 #include "mozilla/dom/BoxObject.h"
-#include "gfxVR.h"
 #include "gfxPrefs.h"
+#include "VRManager.h"
 #include "nsISupportsPrimitives.h"
 
 #include "mozilla/DocLoadingTimelineMarker.h"
 
 #include "nsISpeculativeConnect.h"
 
 #ifdef MOZ_MEDIA_NAVIGATOR
 #include "mozilla/MediaManager.h"
@@ -11040,16 +11040,17 @@ public:
     // MozDOMFullscreen:Entered dispatched.
     nsIDocument* lastDocument = mDocuments[mDocuments.Length() - 1];
     nsContentUtils::DispatchEventOnlyToChrome(
       lastDocument, ToSupports(lastDocument),
       NS_LITERAL_STRING("MozDOMFullscreen:Exited"),
       /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
     // Ensure the window exits fullscreen.
     if (nsPIDOMWindow* win = mDocuments[0]->GetWindow()) {
+      win->SetVRHMDDeviceIndex(0);
       win->SetFullscreenInternal(nsPIDOMWindow::eForForceExitFullscreen, false);
     }
     return NS_OK;
   }
 
 private:
   nsCOMArray<nsIDocument> mDocuments;
 };
@@ -11427,44 +11428,40 @@ IsInActiveTab(nsIDocument* aDoc)
   fm->GetActiveWindow(getter_AddRefs(activeWindow));
   if (!activeWindow) {
     return false;
   }
 
   return activeWindow == rootWin;
 }
 
-nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement)
+nsresult nsDocument::RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
+                                                  PRInt32 aVRDeviceIndex)
 {
   // Ensure the frame element is the fullscreen element in this document.
   // If the frame element is already the fullscreen element in this document,
   // this has no effect.
   nsCOMPtr<nsIContent> content(do_QueryInterface(aFrameElement));
   auto request = MakeUnique<FullscreenRequest>(content->AsElement());
   request->mIsCallerChrome = false;
   request->mShouldNotifyNewOrigin = false;
+  if (aVRDeviceIndex > 0) {
+    request->mVRHMDDeviceIndex = aVRDeviceIndex;
+  }
   RequestFullScreen(Move(request));
 
   return NS_OK;
 }
 
 nsresult nsDocument::RemoteFrameFullscreenReverted()
 {
   RestorePreviousFullScreenState();
   return NS_OK;
 }
 
-static void
-ReleaseVRDeviceProxyRef(void *, nsIAtom*, void *aPropertyValue, void *)
-{
-  if (aPropertyValue) {
-    static_cast<gfx::VRDeviceProxy*>(aPropertyValue)->Release();
-  }
-}
-
 bool
 nsDocument::FullscreenElementReadyCheck(Element* aElement,
                                         bool aWasCallerChrome)
 {
   NS_ASSERTION(aElement,
     "Must pass non-null element to nsDocument::RequestFullScreen");
   if (!aElement || aElement == GetFullScreenElement()) {
     return false;
@@ -11700,28 +11697,36 @@ nsDocument::RequestFullScreen(UniquePtr<
   // We don't need to check element ready before this point, because
   // if we called ApplyFullscreen, it would check that for us.
   Element* elem = aRequest->GetElement();
   if (!FullscreenElementReadyCheck(elem, aRequest->mIsCallerChrome)) {
     return;
   }
 
   PendingFullscreenRequestList::Add(Move(aRequest));
+  const FullscreenRequest*
+    lastRequest = PendingFullscreenRequestList::GetLast();
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     // If we are not the top level process, dispatch an event to make
     // our parent process go fullscreen first.
+
+    // First, if we have a HMD, inform the root window
+    if (lastRequest->mVRHMDDeviceIndex) {
+      rootWin->SetVRHMDDeviceIndex(lastRequest->mVRHMDDeviceIndex);
+    }
+
+    // Then send an event to chrome, which will be turned into a message
+    // to the parent.
     nsContentUtils::DispatchEventOnlyToChrome(
       this, ToSupports(this), NS_LITERAL_STRING("MozDOMFullscreen:Request"),
       /* Bubbles */ true, /* Cancelable */ false, /* DefaultAction */ nullptr);
   } else {
     // Make the window fullscreen.
-    const FullscreenRequest*
-      lastRequest = PendingFullscreenRequestList::GetLast();
     rootWin->SetFullscreenInternal(nsPIDOMWindow::eForFullscreenAPI, true,
-                                   lastRequest->mVRHMDDevice);
+                                   lastRequest->mVRHMDDeviceIndex);
   }
 }
 
 /* static */ bool
 nsIDocument::HandlePendingFullscreenRequests(nsIDocument* aDoc)
 {
   bool handled = false;
   PendingFullscreenRequestList::Iterator iter(
@@ -11768,21 +11773,21 @@ nsDocument::ApplyFullscreen(const Fullsc
   // Remember the root document, so that if a full-screen document is hidden
   // we can reset full-screen state in the remaining visible full-screen documents.
   nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
 
   // If a document is already in fullscreen, then unlock the mouse pointer
   // before setting a new document to fullscreen
   UnlockPointer();
 
-  // Process options -- in this case, just HMD
-  if (aRequest.mVRHMDDevice) {
-    RefPtr<gfx::VRDeviceProxy> hmdRef = aRequest.mVRHMDDevice;
-    elem->SetProperty(nsGkAtoms::vr_state, hmdRef.forget().take(),
-                      ReleaseVRDeviceProxyRef, true);
+  // vr_state
+  if (aRequest.mVRHMDDeviceIndex) {
+    elem->SetProperty(nsGkAtoms::vr_state,
+                      reinterpret_cast<void*>(aRequest.mVRHMDDeviceIndex),
+                      nullptr, true);
   }
 
   // Set the full-screen element. This sets the full-screen style on the
   // element, and the full-screen-ancestor styles on ancestors of the element
   // in this document.
   DebugOnly<bool> x = FullScreenStackPush(elem);
   NS_ASSERTION(x, "Full-screen state of requesting doc should always change!");
   // Set the iframe fullscreen flag.
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -106,27 +106,28 @@ struct FullscreenRequest : public Linked
   Element* GetElement() const { return mElement; }
   nsDocument* GetDocument() const { return mDocument; }
 
 private:
   RefPtr<Element> mElement;
   RefPtr<nsDocument> mDocument;
 
 public:
-  RefPtr<gfx::VRDeviceProxy> mVRHMDDevice;
   // This value should be true if the fullscreen request is
   // originated from chrome code.
   bool mIsCallerChrome = false;
   // This value denotes whether we should trigger a NewOrigin event if
   // requesting fullscreen in its document causes the origin which is
   // fullscreen to change. We may want *not* to trigger that event if
   // we're calling RequestFullScreen() as part of a continuation of a
   // request in a subdocument in different process, whereupon the caller
   // need to send some notification itself with the real origin.
   bool mShouldNotifyNewOrigin = true;
+  // The VR device index, if any.
+  uint32_t mVRHMDDeviceIndex = 0;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 /**
  * Right now our identifier map entries contain information for 'name'
  * and 'id' mappings of a given string. This is so that
@@ -1193,18 +1194,18 @@ public:
 
   virtual Element* GetFullScreenElement() override;
   virtual nsTArray<Element*> GetFullscreenStack() const override;
   virtual void AsyncRequestFullScreen(
     mozilla::UniquePtr<FullscreenRequest>&& aRequest) override;
   virtual void RestorePreviousFullScreenState() override;
   virtual bool IsFullscreenLeaf() override;
   virtual bool IsFullScreenDoc() override;
-  virtual nsresult
-    RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) override;
+  virtual nsresult RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
+                                                PRInt32 aVRDeviceIndex = 0) override;
 
   virtual nsresult RemoteFrameFullscreenReverted() override;
   virtual nsIDocument* GetFullscreenRoot() override;
   virtual void SetFullscreenRoot(nsIDocument* aRoot) override;
 
   // Returns the size of the mBlockedTrackingNodes array. (nsIDocument.h)
   //
   // This array contains nodes that have been blocked to prevent
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -186,16 +186,17 @@
 #include "mozilla/dom/indexedDB/IDBFactory.h"
 #include "mozilla/dom/Promise.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/Gamepad.h"
 #include "mozilla/dom/GamepadService.h"
 #endif
 
+#include "VRManager.h"
 #include "mozilla/dom/VRDevice.h"
 
 #include "nsRefreshDriver.h"
 #include "Layers.h"
 
 #include "mozilla/AddonPathService.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/Services.h"
@@ -5987,17 +5988,17 @@ FullscreenTransitionTask::Observer::Obse
     obs->RemoveObserver(this, kPaintedTopic);
     mTask->mTimer = nullptr;
     mTask->Run();
   }
   return NS_OK;
 }
 
 static bool
-MakeWidgetFullscreen(nsGlobalWindow* aWindow, gfx::VRDeviceProxy* aHMD,
+MakeWidgetFullscreen(nsGlobalWindow* aWindow, uint32_t aVRDeviceIndex,
                      nsPIDOMWindow::FullscreenReason aReason, bool aFullscreen)
 {
   nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
   if (!widget) {
     return false;
   }
 
   FullscreenTransitionDuration duration;
@@ -6005,32 +6006,41 @@ MakeWidgetFullscreen(nsGlobalWindow* aWi
   nsCOMPtr<nsISupports> transitionData;
   if (aReason == nsPIDOMWindow::eForFullscreenAPI) {
     GetFullscreenTransitionDuration(aFullscreen, &duration);
     if (!duration.IsSuppressed()) {
       performTransition = widget->
         PrepareForFullscreenTransition(getter_AddRefs(transitionData));
     }
   }
-  nsCOMPtr<nsIScreen> screen = aHMD ? aHMD->GetScreen() : nullptr;
+  
+  nsCOMPtr<nsIScreen> screen;
+  RefPtr<gfx::VRHMDInfo> hmd;
+  if (!XRE_IsContentProcess() && aVRDeviceIndex) {
+    hmd = gfx::VRManager::Get()->GetDevice(aVRDeviceIndex);
+    screen = hmd->GetScreen();
+  }
+
+  widget->SetAttachedHMD(hmd);
+
   if (!performTransition) {
     return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget, screen);
-  } else {
-    nsCOMPtr<nsIRunnable> task =
-      new FullscreenTransitionTask(duration, aWindow, aFullscreen,
-                                   widget, screen, transitionData);
-    task->Run();
-    return true;
-  }
+  }
+
+  nsCOMPtr<nsIRunnable> task =
+    new FullscreenTransitionTask(duration, aWindow, aFullscreen,
+                                 widget, screen, transitionData);
+  task->Run();
+  return true;
 }
 
 nsresult
 nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason,
                                       bool aFullScreen,
-                                      gfx::VRDeviceProxy* aHMD)
+                                      uint32_t aVRDeviceIndex)
 {
   MOZ_ASSERT(IsOuterWindow());
   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
              "Requires safe to run script as it "
              "may call FinishDOMFullscreenChange");
 
   NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
 
@@ -6048,17 +6058,17 @@ nsGlobalWindow::SetFullscreenInternal(Fu
   // via the DocShell tree, and if we are not already the root,
   // call SetFullScreen on that window instead.
   nsCOMPtr<nsIDocShellTreeItem> rootItem;
   mDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
   nsCOMPtr<nsPIDOMWindow> window = rootItem ? rootItem->GetWindow() : nullptr;
   if (!window)
     return NS_ERROR_FAILURE;
   if (rootItem != mDocShell)
-    return window->SetFullscreenInternal(aReason, aFullScreen, aHMD);
+    return window->SetFullscreenInternal(aReason, aFullScreen, aVRDeviceIndex);
 
   // make sure we don't try to set full screen on a non-chrome window,
   // which might happen in embedding world
   if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
     return NS_ERROR_FAILURE;
 
   // If we are already in full screen mode, just return.
   if (mFullScreen == aFullScreen)
@@ -6099,17 +6109,17 @@ nsGlobalWindow::SetFullscreenInternal(Fu
   // gone full screen, the state trap above works.
   mFullScreen = aFullScreen;
 
   // Sometimes we don't want the top-level widget to actually go fullscreen,
   // for example in the B2G desktop client, we don't want the emulated screen
   // dimensions to appear to increase when entering fullscreen mode; we just
   // want the content to fill the entire client area of the emulator window.
   if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) {
-    if (MakeWidgetFullscreen(this, aHMD, aReason, aFullScreen)) {
+    if (MakeWidgetFullscreen(this, aVRDeviceIndex, aReason, aFullScreen)) {
       // The rest of code for switching fullscreen is in nsGlobalWindow::
       // FinishFullscreenChange() which will be called after sizemodechange
       // event is dispatched.
       return NS_OK;
     }
   }
 
   // If we didn't setup the widget, we may need to manually set this
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -466,17 +466,17 @@ public:
   virtual void RefreshCompartmentPrincipal() override;
 
   // For accessing protected field mFullScreen
   friend class FullscreenTransitionTask;
 
   // Outer windows only.
   virtual nsresult SetFullscreenInternal(
     FullscreenReason aReason, bool aIsFullscreen,
-    mozilla::gfx::VRDeviceProxy *aHMD = nullptr) override final;
+    uint32_t aVRDeviceIndex = 0) override final;
   virtual void FinishFullscreenChange(bool aIsFullscreen) override final;
   bool SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen,
                            nsIWidget* aWidget, nsIScreen* aScreen);
   bool FullScreen() const;
 
   // Inner windows only.
   virtual void SetHasGamepadEventListener(bool aHasGamepad = true) override;
 
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1156,19 +1156,22 @@ public:
   virtual void AsyncRequestFullScreen(
     mozilla::UniquePtr<FullscreenRequest>&& aRequest) = 0;
 
   /**
    * Called when a frame in a child process has entered fullscreen or when a
    * fullscreen frame in a child process changes to another origin.
    * aFrameElement is the frame element which contains the child-process
    * fullscreen document.
+   *
+   * If nonzero, the fullscreen happens on the given VR HMD device index.
    */
   virtual nsresult
-    RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement) = 0;
+    RemoteFrameFullscreenChanged(nsIDOMElement* aFrameElement,
+                                 PRInt32 aVRDeviceIndex = 0) = 0;
 
   /**
    * Called when a frame in a remote child document has rolled back fullscreen
    * so that all its fullscreen element stacks are empty; we must continue the
    * rollback in this parent process' doc tree branch which is fullscreen.
    * Note that only one branch of the document tree can have its documents in
    * fullscreen state at one time. We're in inconsistent state if a
    * fullscreen document has a parent and that parent isn't fullscreen. We
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -36,19 +36,16 @@ class nsXBLPrototypeHandler;
 struct nsTimeout;
 
 namespace mozilla {
 namespace dom {
 class AudioContext;
 class Element;
 class ServiceWorkerRegistrationMainThread;
 } // namespace dom
-namespace gfx {
-class VRDeviceProxy;
-} // namespace gfx
 } // namespace mozilla
 
 // Popup control state enum. The values in this enum must go from most
 // permissive to least permissive so that it's safe to push state in
 // all situations. Pushing popup state onto the stack never makes the
 // current popup state less permissive (see
 // nsGlobalWindow::PushPopupControlState()).
 enum PopupControlState {
@@ -500,24 +497,24 @@ public:
     // suppress the fullscreen transition.
     eForForceExitFullscreen
   };
 
   /**
    * Moves the top-level window into fullscreen mode if aIsFullScreen is true,
    * otherwise exits fullscreen.
    *
-   * If aHMD is not null, the window is made full screen on the given VR HMD
+   * If aVRDeviceIndex is not 0, the window is made full screen on the given VR HMD
    * device instead of its currrent display.
    *
    * Outer windows only.
    */
   virtual nsresult SetFullscreenInternal(
     FullscreenReason aReason, bool aIsFullscreen,
-    mozilla::gfx::VRDeviceProxy *aHMD = nullptr) = 0;
+    uint32_t aVRDeviceIndex = 0) = 0;
 
   /**
    * This function should be called when the fullscreen state is flipped.
    * If no widget is involved the fullscreen change, this method is called
    * by SetFullscreenInternal, otherwise, it is called when the widget
    * finishes its change to or from fullscreen.
    *
    * @param aIsFullscreen indicates whether the widget is in fullscreen.
@@ -815,16 +812,19 @@ public:
   virtual nsresult SetFullScreen(bool aFullScreen) = 0;
 
   virtual nsresult Focus() = 0;
   virtual nsresult Close() = 0;
 
   virtual nsresult MoveBy(int32_t aXDif, int32_t aYDif) = 0;
   virtual nsresult UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) = 0;
 
+  virtual void SetVRHMDDeviceIndex(uint32_t aDeviceIndex) { mVRHMDDeviceIndex = aDeviceIndex; }
+  virtual uint32_t GetVRHMDDeviceIndex() const { return mVRHMDDeviceIndex; }
+
 protected:
   // The nsPIDOMWindow constructor. The aOuterWindow argument should
   // be null if and only if the created window itself is an outer
   // window. In all other cases aOuterWindow should be the outer
   // window for the inner window that is being created.
   explicit nsPIDOMWindow(nsPIDOMWindow *aOuterWindow);
 
   ~nsPIDOMWindow();
@@ -922,16 +922,19 @@ protected:
   // the (chrome|content)-document-global-created notification.
   bool mHasNotifiedGlobalCreated;
 
   uint32_t mMarkedCCGeneration;
 
   // Let the service workers plumbing know that some feature are enabled while
   // testing.
   bool mServiceWorkersTestingEnabled;
+
+  // The VR HMD device index attached to this window, if any
+  uint32_t mVRHMDDeviceIndex = 0;
 };
 
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindow, NS_PIDOMWINDOW_IID)
 
 #ifdef MOZILLA_INTERNAL_API
 PopupControlState
 PushPopupControlState(PopupControlState aState, bool aForce);
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -1175,18 +1175,22 @@ interface nsIDOMWindowUtils : nsISupport
    */
   const unsigned long QUERY_CHARACTER_AT_POINT                  = 3208;
 
   /**
    * Called when the remote child frame has changed its fullscreen state,
    * when entering fullscreen, and when the origin which is fullscreen changes.
    * aFrameElement is the iframe element which contains the child-process
    * fullscreen document.
+   *
+   * If specified and nonzero, the fullscreen takes place on the given HMD
+   * device index.
    */
-  void remoteFrameFullscreenChanged(in nsIDOMElement aFrameElement);
+  void remoteFrameFullscreenChanged(in nsIDOMElement aFrameElement,
+                                    [optional] in long aVRHMDDeviceIndex);
 
   /**
    * Called when the remote frame has popped all fullscreen elements off its
    * stack, so that the operation can complete on the parent side.
    */
   void remoteFrameFullscreenReverted();
 
   /**
@@ -1843,16 +1847,24 @@ interface nsIDOMWindowUtils : nsISupport
    * document's counters.  Normally, use counters are flushed to telemetry
    * upon document destruction, but as document destruction is somewhat
    * non-deterministic, we have this method here for more determinism when
    * running tests.
    */
   void forceUseCounterFlush(in nsIDOMNode aNode);
 
   void setNextPaintSyncId(in long aSyncId);
+
+  /**
+   * The VR HMD device ID currently attached to this window.
+   * 0 if no VR HMD device is not attached.
+   *
+   * Will throw a DOM security error if called without chrome privileges.
+   */
+  readonly attribute unsigned long vrHMDDeviceID;
 };
 
 [scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)]
 interface nsITranslationNodeList : nsISupports {
   readonly attribute unsigned long length;
   nsIDOMNode item(in unsigned long index);
 
   // A translation root is a block element, or an inline element
--- a/gfx/thebes/gfxVsync.cpp
+++ b/gfx/thebes/gfxVsync.cpp
@@ -234,17 +234,16 @@ VsyncManager::Shutdown()
  * SoftwareVsyncSource implementation
  */
 
 SoftwareVsyncSource::SoftwareVsyncSource(const nsID& aSourceID, double aInterval)
   : VsyncSource(aSourceID)
   , mCurrentVsyncTask(nullptr)
   , mVsyncEnabled(false)
 {
-  MOZ_ASSERT(NS_IsMainThread());
   mVsyncInterval = mozilla::TimeDuration::FromMilliseconds(aInterval);
   mVsyncThread = new base::Thread("SoftwareVsyncThread");
   MOZ_RELEASE_ASSERT(mVsyncThread->Start(), "Could not start software vsync thread");
 }
 
 SoftwareVsyncSource::~SoftwareVsyncSource()
 {
 }
@@ -294,17 +293,16 @@ SoftwareVsyncSource::InternalDisableVsyn
     mCurrentVsyncTask->Cancel();
     mCurrentVsyncTask = nullptr;
   }
 }
 
 bool
 SoftwareVsyncSource::IsVsyncEnabled()
 {
-  MOZ_ASSERT(NS_IsMainThread());
   return mVsyncEnabled;
 }
 
 bool
 SoftwareVsyncSource::IsInSoftwareVsyncThread()
 {
   return mVsyncThread->thread_id() == PlatformThread::CurrentId();
 }
--- a/gfx/vr/VRDeviceProxy.cpp
+++ b/gfx/vr/VRDeviceProxy.cpp
@@ -8,17 +8,16 @@
 #include "prlink.h"
 #include "prmem.h"
 #include "prenv.h"
 #include "gfxPrefs.h"
 #include "nsString.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/unused.h"
 #include "nsServiceManagerUtils.h"
-#include "nsIScreenManager.h"
 
 
 #ifdef XP_WIN
 #include "../layers/d3d11/CompositorD3D11.h"
 #endif
 
 #include "VRDeviceProxy.h"
 #include "VRManagerChild.h"
@@ -26,34 +25,16 @@
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 VRDeviceProxy::VRDeviceProxy(const VRDeviceUpdate& aDeviceUpdate)
   : mDeviceInfo(aDeviceUpdate.mDeviceInfo)
   , mSensorState(aDeviceUpdate.mSensorState)
 {
   MOZ_COUNT_CTOR(VRDeviceProxy);
-
-  if (mDeviceInfo.mScreenRect.width && mDeviceInfo.mScreenRect.height) {
-    if (mDeviceInfo.mIsFakeScreen) {
-      mScreen = MakeFakeScreen(mDeviceInfo.mScreenRect);
-    } else {
-      nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
-      if (screenmgr) {
-        screenmgr->ScreenForRect(mDeviceInfo.mScreenRect.x, mDeviceInfo.mScreenRect.y,
-                                 mDeviceInfo.mScreenRect.width, mDeviceInfo.mScreenRect.height,
-                                 getter_AddRefs(mScreen));
-      }
-    }
-#ifdef DEBUG
-    printf_stderr("VR DEVICE SCREEN: %d %d %d %d\n",
-                  mDeviceInfo.mScreenRect.x, mDeviceInfo.mScreenRect.y,
-                  mDeviceInfo.mScreenRect.width, mDeviceInfo.mScreenRect.height);
-#endif
-  }
 }
 
 VRDeviceProxy::~VRDeviceProxy() {
   MOZ_COUNT_DTOR(VRDeviceProxy);
 }
 
 void
 VRDeviceProxy::UpdateDeviceInfo(const VRDeviceUpdate& aDeviceUpdate)
@@ -86,68 +67,8 @@ VRDeviceProxy::GetSensorState(double tim
   return mSensorState;
 }
 
 void
 VRDeviceProxy::UpdateSensorState(const VRHMDSensorState& aSensorState)
 {
   mSensorState = aSensorState;
 }
-
-// Dummy nsIScreen implementation, for when we just need to specify a size
-class FakeScreen : public nsIScreen
-{
-public:
-  explicit FakeScreen(const IntRect& aScreenRect)
-    : mScreenRect(aScreenRect)
-  { }
-
-  NS_DECL_ISUPPORTS
-
-  NS_IMETHOD GetRect(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
-    *l = mScreenRect.x;
-    *t = mScreenRect.y;
-    *w = mScreenRect.width;
-    *h = mScreenRect.height;
-    return NS_OK;
-  }
-  NS_IMETHOD GetAvailRect(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
-    return GetRect(l, t, w, h);
-  }
-  NS_IMETHOD GetRectDisplayPix(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
-    return GetRect(l, t, w, h);
-  }
-  NS_IMETHOD GetAvailRectDisplayPix(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
-    return GetAvailRect(l, t, w, h);
-  }
-
-  NS_IMETHOD GetId(uint32_t* aId) override { *aId = (uint32_t)-1; return NS_OK; }
-  NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) override { *aPixelDepth = 24; return NS_OK; }
-  NS_IMETHOD GetColorDepth(int32_t* aColorDepth) override { *aColorDepth = 24; return NS_OK; }
-
-  NS_IMETHOD LockMinimumBrightness(uint32_t aBrightness) override { return NS_ERROR_NOT_AVAILABLE; }
-  NS_IMETHOD UnlockMinimumBrightness(uint32_t aBrightness) override { return NS_ERROR_NOT_AVAILABLE; }
-  NS_IMETHOD GetRotation(uint32_t* aRotation) override {
-    *aRotation = nsIScreen::ROTATION_0_DEG;
-    return NS_OK;
-  }
-  NS_IMETHOD SetRotation(uint32_t aRotation) override { return NS_ERROR_NOT_AVAILABLE; }
-  NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override {
-    *aContentsScaleFactor = 1.0;
-    return NS_OK;
-  }
-
-protected:
-  virtual ~FakeScreen() {}
-
-  IntRect mScreenRect;
-};
-
-NS_IMPL_ISUPPORTS(FakeScreen, nsIScreen)
-
-
-/* static */ already_AddRefed<nsIScreen>
-VRDeviceProxy::MakeFakeScreen(const IntRect& aScreenRect)
-{
-  nsCOMPtr<nsIScreen> screen = new FakeScreen(aScreenRect);
-  return screen.forget();
-}
-
--- a/gfx/vr/VRDeviceProxy.h
+++ b/gfx/vr/VRDeviceProxy.h
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
 #ifndef GFX_VR_PROXY_H
 #define GFX_VR_PROXY_H
 
-#include "nsIScreen.h"
 #include "nsCOMPtr.h"
 #include "mozilla/RefPtr.h"
 
 #include "gfxVR.h"
 
 namespace mozilla {
 namespace gfx {
 
@@ -30,28 +29,19 @@ public:
   const VRDeviceInfo& GetDeviceInfo() const { return mDeviceInfo; }
   virtual VRHMDSensorState GetSensorState(double timeOffset = 0.0);
 
   bool SetFOV(const VRFieldOfView& aFOVLeft, const VRFieldOfView& aFOVRight,
               double zNear, double zFar);
 
   virtual void ZeroSensor();
 
-
-  // The nsIScreen that represents this device
-  nsIScreen* GetScreen() { return mScreen; }
-
 protected:
   virtual ~VRDeviceProxy();
 
   VRDeviceInfo mDeviceInfo;
   VRHMDSensorState mSensorState;
-
-  nsCOMPtr<nsIScreen> mScreen;
-
-  static already_AddRefed<nsIScreen> MakeFakeScreen(const IntRect& aScreenRect);
-
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* GFX_VR_PROXY_H */
--- a/gfx/vr/gfxVR.cpp
+++ b/gfx/vr/gfxVR.cpp
@@ -4,26 +4,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <math.h>
 
 #include "prlink.h"
 #include "prmem.h"
 #include "prenv.h"
 #include "nsString.h"
+#include "nsIScreenManager.h"
 
 #include "gfxPrefs.h"
 #include "gfxVR.h"
 #if defined(XP_WIN)
 #include "gfxVROculus.h"
 #endif
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX)
 #include "gfxVROculus050.h"
 #endif
 #include "gfxVRCardboard.h"
+#include "gfxVsync.h"
 
 #include "mozilla/unused.h"
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/TextureHost.h"
 
 #ifndef M_PI
 # define M_PI 3.14159265358979323846
 #endif
@@ -34,23 +36,54 @@ using namespace mozilla::gfx;
 Atomic<uint32_t> VRHMDManager::sDeviceBase(0);
 
 VRHMDInfo::VRHMDInfo(VRHMDType aType, bool aUseMainThreadOrientation)
 {
   MOZ_COUNT_CTOR(VRHMDInfo);
   mDeviceInfo.mType = aType;
   mDeviceInfo.mDeviceID = VRHMDManager::AllocateDeviceID();
   mDeviceInfo.mUseMainThreadOrientation = aUseMainThreadOrientation;
+  mDeviceInfo.mVsyncSourceID = VsyncManager::kGlobalDisplaySourceID;
 }
 
 VRHMDInfo::~VRHMDInfo()
 {
   MOZ_COUNT_DTOR(VRHMDInfo);
 }
 
+nsIScreen*
+VRHMDInfo::GetScreen()
+{
+  if (mDeviceInfo.mIsFakeScreen &&
+      mDeviceInfo.mScreenRect.width &&
+      mDeviceInfo.mScreenRect.height)
+  {
+    mScreen = MakeFakeScreen(mDeviceInfo.mScreenRect);
+  } else {
+    nsCOMPtr<nsIScreenManager> screenmgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+    if (screenmgr) {
+      screenmgr->ScreenForRect(mDeviceInfo.mScreenRect.x, mDeviceInfo.mScreenRect.y,
+                               mDeviceInfo.mScreenRect.width, mDeviceInfo.mScreenRect.height,
+                               getter_AddRefs(mScreen));
+      if (mScreen) {
+        mScreen->GetRect(&mDeviceInfo.mScreenRect.x, &mDeviceInfo.mScreenRect.y,
+                         &mDeviceInfo.mScreenRect.width, &mDeviceInfo.mScreenRect.height);
+      }
+    }
+  }
+
+#ifdef DEBUG
+  printf_stderr("VR DEVICE SCREEN RECT: %d %d %d %d\n",
+                mDeviceInfo.mScreenRect.x, mDeviceInfo.mScreenRect.y,
+                mDeviceInfo.mScreenRect.width, mDeviceInfo.mScreenRect.height);
+#endif
+
+  return mScreen;
+}
+
 /* static */ uint32_t
 VRHMDManager::AllocateDeviceID()
 {
   return ++sDeviceBase;
 }
 
 VRHMDRenderingSupport::RenderTargetSet::RenderTargetSet()
   : currentRenderTarget(0)
@@ -88,8 +121,68 @@ VRFieldOfView::ConstructProjectionMatrix
   m[2*4+2] = zFar / (zNear - zFar) * -handednessScale;
   m[3*4+2] = (zFar * zNear) / (zNear - zFar);
 
   m[2*4+3] = handednessScale;
   m[3*4+3] = 0.0f;
 
   return mobj;
 }
+
+// Dummy nsIScreen implementation, for when we just need to specify a size
+class FakeScreen : public nsIScreen
+{
+public:
+  explicit FakeScreen(const IntRect& aScreenRect)
+    : mScreenRect(aScreenRect)
+  { }
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD GetRect(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
+    *l = mScreenRect.x;
+    *t = mScreenRect.y;
+    *w = mScreenRect.width;
+    *h = mScreenRect.height;
+    return NS_OK;
+  }
+  NS_IMETHOD GetAvailRect(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
+    return GetRect(l, t, w, h);
+  }
+  NS_IMETHOD GetRectDisplayPix(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
+    return GetRect(l, t, w, h);
+  }
+  NS_IMETHOD GetAvailRectDisplayPix(int32_t *l, int32_t *t, int32_t *w, int32_t *h) override {
+    return GetAvailRect(l, t, w, h);
+  }
+
+  NS_IMETHOD GetId(uint32_t* aId) override { *aId = (uint32_t)-1; return NS_OK; }
+  NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth) override { *aPixelDepth = 24; return NS_OK; }
+  NS_IMETHOD GetColorDepth(int32_t* aColorDepth) override { *aColorDepth = 24; return NS_OK; }
+
+  NS_IMETHOD LockMinimumBrightness(uint32_t aBrightness) override { return NS_ERROR_NOT_AVAILABLE; }
+  NS_IMETHOD UnlockMinimumBrightness(uint32_t aBrightness) override { return NS_ERROR_NOT_AVAILABLE; }
+  NS_IMETHOD GetRotation(uint32_t* aRotation) override {
+    *aRotation = nsIScreen::ROTATION_0_DEG;
+    return NS_OK;
+  }
+  NS_IMETHOD SetRotation(uint32_t aRotation) override { return NS_ERROR_NOT_AVAILABLE; }
+  NS_IMETHOD GetContentsScaleFactor(double* aContentsScaleFactor) override {
+    *aContentsScaleFactor = 1.0;
+    return NS_OK;
+  }
+
+protected:
+  virtual ~FakeScreen() {}
+
+  IntRect mScreenRect;
+};
+
+NS_IMPL_ISUPPORTS(FakeScreen, nsIScreen)
+
+
+/* static */ already_AddRefed<nsIScreen>
+VRHMDInfo::MakeFakeScreen(const IntRect& aScreenRect)
+{
+  nsCOMPtr<nsIScreen> screen = new FakeScreen(aScreenRect);
+  return screen.forget();
+}
+
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -2,18 +2,20 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_VR_H
 #define GFX_VR_H
 
 #include "nsTArray.h"
+#include "nsID.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
+#include "nsIScreen.h"
 #include "mozilla/RefPtr.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnumBits.h"
 
@@ -99,16 +101,18 @@ struct VRDeviceInfo
   const VRFieldOfView& GetMaximumEyeFOV(uint32_t whichEye) const { return mMaximumEyeFOV[whichEye]; }
 
   const IntSize& SuggestedEyeResolution() const { return mEyeResolution; }
   const Point3D& GetEyeTranslation(uint32_t whichEye) const { return mEyeTranslation[whichEye]; }
   const Matrix4x4& GetEyeProjectionMatrix(uint32_t whichEye) const { return mEyeProjectionMatrix[whichEye]; }
   const VRFieldOfView& GetEyeFOV(uint32_t whichEye) const { return mEyeFOV[whichEye]; }
   bool GetUseMainThreadOrientation() const { return mUseMainThreadOrientation; }
 
+  const nsID& GetVsyncSourceID() const { return mVsyncSourceID; }
+
   enum Eye {
     Eye_Left,
     Eye_Right,
     NumEyes
   };
 
   uint32_t mDeviceID;
   VRHMDType mType;
@@ -124,17 +128,17 @@ struct VRDeviceInfo
    * XXX fix this for vertical displays
    */
   IntSize mEyeResolution;
   IntRect mScreenRect;
 
   bool mIsFakeScreen;
   bool mUseMainThreadOrientation;
 
-
+  nsID mVsyncSourceID;
 
   bool operator==(const VRDeviceInfo& other) const {
     return mType == other.mType &&
            mDeviceID == other.mDeviceID &&
            mDeviceName == other.mDeviceName &&
            mSupportedSensorBits == other.mSupportedSensorBits &&
            mEyeResolution == other.mEyeResolution &&
            mScreenRect == other.mScreenRect &&
@@ -144,17 +148,18 @@ struct VRDeviceInfo
            mMaximumEyeFOV[1] == other.mMaximumEyeFOV[1] &&
            mRecommendedEyeFOV[0] == other.mRecommendedEyeFOV[0] &&
            mRecommendedEyeFOV[1] == other.mRecommendedEyeFOV[1] &&
            mEyeFOV[0] == other.mEyeFOV[0] &&
            mEyeFOV[1] == other.mEyeFOV[1] &&
            mEyeTranslation[0] == other.mEyeTranslation[0] &&
            mEyeTranslation[1] == other.mEyeTranslation[1] &&
            mEyeProjectionMatrix[0] == other.mEyeProjectionMatrix[0] &&
-           mEyeProjectionMatrix[1] == other.mEyeProjectionMatrix[1];
+           mEyeProjectionMatrix[1] == other.mEyeProjectionMatrix[1] &&
+           mVsyncSourceID == other.mVsyncSourceID;
   }
 
   bool operator!=(const VRDeviceInfo& other) const {
     return !(*this == other);
   }
 };
 
 
@@ -262,16 +267,20 @@ public:
                       double zNear, double zFar) = 0;
 
   virtual bool KeepSensorTracking() = 0;
   virtual void NotifyVsync(const TimeStamp& aVsyncTimestamp) = 0;
   virtual VRHMDSensorState GetSensorState(double timeOffset = 0.0) = 0;
 
   virtual void ZeroSensor() = 0;
 
+  // The nsIScreen for this device -- really just the boundaries, will
+  // likely be a fake screen.
+  nsIScreen* GetScreen();
+
   // if rendering is offloaded
   virtual VRHMDRenderingSupport *GetRenderingSupport() { return nullptr; }
 
   // distortion mesh stuff; we should implement renderingsupport for this
   virtual void FillDistortionConstants(uint32_t whichEye,
                                        const IntSize& textureSize, // the full size of the texture
                                        const IntRect& eyeViewport, // the viewport within the texture for the current eye
                                        const Size& destViewport,   // the size of the destination viewport
@@ -281,16 +290,19 @@ public:
   const VRDistortionMesh& GetDistortionMesh(uint32_t whichEye) const { return mDistortionMesh[whichEye]; }
 protected:
   explicit VRHMDInfo(VRHMDType aType, bool aUseMainThreadOrientation);
   virtual ~VRHMDInfo();
 
   VRHMDConfiguration mConfiguration;
   VRDeviceInfo mDeviceInfo;
   VRDistortionMesh mDistortionMesh[VRDeviceInfo::NumEyes];
+  nsCOMPtr<nsIScreen> mScreen;
+
+  static already_AddRefed<nsIScreen> MakeFakeScreen(const IntRect& aScreenRect);
 };
 
 class VRHMDManager {
 public:
   static uint32_t AllocateDeviceID();
 
 protected:
   static Atomic<uint32_t> sDeviceBase;
--- a/gfx/vr/gfxVRCardboard.cpp
+++ b/gfx/vr/gfxVRCardboard.cpp
@@ -41,17 +41,17 @@ HMDInfoCardboard::HMDInfoCardboard()
   mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Right] = gfx::VRFieldOfView(45.0, 45.0, 45.0, 45.0);
 
   SetFOV(mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Left], mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Right], 0.01, 10000.0);
 
   mDeviceInfo.mScreenRect.x = 0;
   mDeviceInfo.mScreenRect.y = 0;
   mDeviceInfo.mScreenRect.width = 1920;
   mDeviceInfo.mScreenRect.height = 1080;
-  mDeviceInfo.mIsFakeScreen = true;
+  mDeviceInfo.mIsFakeScreen = false;
 }
 
 
 VRHMDSensorState
 HMDInfoCardboard::GetSensorState(double timeOffset)
 {
   // Actual sensor state is calculated on the main thread,
   // within VRDeviceProxyOrientationFallBack
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -3,22 +3,27 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <math.h>
 
 #include "prlink.h"
 #include "prmem.h"
 #include "prenv.h"
+#include "gfxPlatform.h"
 #include "gfxPrefs.h"
+#include "gfxUtils.h"
 #include "nsString.h"
+#include "gfxVsync.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TimeStamp.h"
 
 #include "mozilla/gfx/Quaternion.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIScreenManager.h"
 
 #ifdef XP_WIN
 #include "../layers/d3d11/CompositorD3D11.h"
 #endif
 
 #include "gfxVROculus.h"
 
 #ifndef M_PI
@@ -245,16 +250,18 @@ FromFovPort(const ovrFovPort& aFOV)
 
 } // namespace
 
 HMDInfoOculus::HMDInfoOculus(ovrSession aSession)
   : VRHMDInfo(VRHMDType::Oculus, false)
   , mSession(aSession)
   , mPerfHudMode(0)
 {
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Can only create HMDInfoOculus in XRE parent process!");
   MOZ_ASSERT(sizeof(HMDInfoOculus::DistortionVertex) == sizeof(VRDistortionVertex),
              "HMDInfoOculus::DistortionVertex must match the size of VRDistortionVertex");
 
   MOZ_COUNT_CTOR_INHERITED(HMDInfoOculus, VRHMDInfo);
 
   mDeviceInfo.mDeviceName.AssignLiteral("Oculus VR HMD");
 
   mDesc = ovr_GetHmdDesc(aSession);
@@ -268,30 +275,41 @@ HMDInfoOculus::HMDInfoOculus(ovrSession 
   }
 
   mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Left] = FromFovPort(mDesc.DefaultEyeFov[ovrEye_Left]);
   mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Right] = FromFovPort(mDesc.DefaultEyeFov[ovrEye_Right]);
 
   mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Left] = FromFovPort(mDesc.MaxEyeFov[ovrEye_Left]);
   mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Right] = FromFovPort(mDesc.MaxEyeFov[ovrEye_Right]);
 
+  // create a vsync source for this display and register it
+  mDeviceInfo.mVsyncSourceID = gfxUtils::GenerateUUID();
+  mVsyncSource = new SoftwareVsyncSource(mDeviceInfo.mVsyncSourceID, 1000.0 / mDesc.DisplayRefreshRate);
+  gfxPlatform::GetPlatform()->GetHardwareVsync()->RegisterSource(mVsyncSource);
+
   uint32_t w = mDesc.Resolution.w;
   uint32_t h = mDesc.Resolution.h;
   mDeviceInfo.mScreenRect.x = 0;
   mDeviceInfo.mScreenRect.y = 0;
   mDeviceInfo.mScreenRect.width = std::max(w, h);
   mDeviceInfo.mScreenRect.height = std::min(w, h);
   mDeviceInfo.mIsFakeScreen = true;
 
   SetFOV(mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Left], mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Right], 0.01, 10000.0);
 }
 
 void
 HMDInfoOculus::Destroy()
 {
+  if (mVsyncSource) {
+    gfxPlatform::GetPlatform()->GetHardwareVsync()->UnregisterSource(mDeviceInfo.mVsyncSourceID);
+    mVsyncSource->Shutdown();
+    mVsyncSource = nullptr;
+  }
+
   if (mSession) {
     ovr_Destroy(mSession);
     mSession = nullptr;
   }
 }
 
 bool
 HMDInfoOculus::SetFOV(const gfx::VRFieldOfView& aFOVLeft, const gfx::VRFieldOfView& aFOVRight,
--- a/gfx/vr/gfxVROculus.h
+++ b/gfx/vr/gfxVROculus.h
@@ -14,16 +14,18 @@
 
 #include "gfxVR.h"
 //#include <OVR_CAPI.h>
 //#include <OVR_CAPI_D3D.h>
 #include "ovr_capi_dynamic.h"
 
 namespace mozilla {
 namespace gfx {
+class VsyncSource;
+
 namespace impl {
 
 class HMDInfoOculus : public VRHMDInfo, public VRHMDRenderingSupport {
 public:
   explicit HMDInfoOculus(ovrSession aSession);
 
   bool SetFOV(const VRFieldOfView& aFOVLeft, const VRFieldOfView& aFOVRight,
               double zNear, double zFar) override;
@@ -64,16 +66,18 @@ protected:
     float genericAttribs[4];
   };
 
   ovrSession mSession;
   ovrHmdDesc mDesc;
   ovrFovPort mFOVPort[2];
   ovrTrackingState mLastTrackingState;
   int32_t mPerfHudMode;
+
+  RefPtr<VsyncSource> mVsyncSource;
 };
 
 } // namespace impl
 
 class VRHMDManagerOculus : public VRHMDManager
 {
 public:
   static already_AddRefed<VRHMDManagerOculus> Create();
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -6225,35 +6225,35 @@ nsDisplaySVGEffects::nsDisplaySVGEffects
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplaySVGEffects::~nsDisplaySVGEffects()
 {
   MOZ_COUNT_DTOR(nsDisplaySVGEffects);
 }
 #endif
 
 nsDisplayVR::nsDisplayVR(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
-                         nsDisplayList* aList, mozilla::gfx::VRDeviceProxy* aHMD)
+                         nsDisplayList* aList, uint32_t aVRDeviceID)
   : nsDisplayOwnLayer(aBuilder, aFrame, aList)
-  , mHMD(aHMD)
+  , mVRDeviceID(aVRDeviceID)
 {
 }
 
 already_AddRefed<Layer>
 nsDisplayVR::BuildLayer(nsDisplayListBuilder* aBuilder,
                         LayerManager* aManager,
                         const ContainerLayerParameters& aContainerParameters)
 {
   ContainerLayerParameters newContainerParameters = aContainerParameters;
   uint32_t flags = FrameLayerBuilder::CONTAINER_NOT_CLIPPED_BY_ANCESTORS |
                    FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR;
   RefPtr<ContainerLayer> container = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
                            newContainerParameters, nullptr, flags);
 
-  container->SetVRDeviceID(mHMD->GetDeviceInfo().GetDeviceID());
+  container->SetVRDeviceID(mVRDeviceID);
   container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(),
                          /*the value is irrelevant*/nullptr);
 
   return container.forget();
 }
 nsRegion nsDisplaySVGEffects::GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
                                               bool* aSnap)
 {
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -4328,26 +4328,26 @@ public:
 /**
  * A wrapper layer that wraps its children in a container, then renders
  * everything with an appropriate VR effect based on the HMDInfo.
  */
 
 class nsDisplayVR : public nsDisplayOwnLayer {
 public:
   nsDisplayVR(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
-              nsDisplayList* aList, mozilla::gfx::VRDeviceProxy* aHMD);
+              nsDisplayList* aList, uint32_t aVRDeviceID);
 
   virtual LayerState GetLayerState(nsDisplayListBuilder* aBuilder,
                                    LayerManager* aManager,
                                    const ContainerLayerParameters& aParameters) override
   {
     return mozilla::LAYER_ACTIVE;
   }
 
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
 
 protected:
-  RefPtr<mozilla::gfx::VRDeviceProxy> mHMD;
+  uint32_t mVRDeviceID;
 };
 
 #endif /*NSDISPLAYLIST_H_*/
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2042,19 +2042,19 @@ nsIFrame::BuildDisplayListForStackingCon
     IsScrollFrameActive(aBuilder,
                         nsLayoutUtils::GetNearestScrollableFrame(GetParent(),
                         nsLayoutUtils::SCROLLABLE_SAME_DOC |
                         nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN));
 
   nsDisplayListBuilder::AutoBuildingDisplayList
     buildingDisplayList(aBuilder, this, dirtyRect, true);
 
-  mozilla::gfx::VRDeviceProxy* vrHMDInfo = nullptr;
+  uint32_t vrDeviceIndex = 0;
   if ((GetStateBits() & NS_FRAME_HAS_VR_CONTENT)) {
-    vrHMDInfo = static_cast<mozilla::gfx::VRDeviceProxy*>(mContent->GetProperty(nsGkAtoms::vr_state));
+    vrDeviceIndex = (uint32_t) reinterpret_cast<uintptr_t>(mContent->GetProperty(nsGkAtoms::vr_state));
   }
 
   DisplayListClipState::AutoSaveRestore clipState(aBuilder);
 
   nsDisplayListBuilder::AutoSaveRestorePerspectiveIndex perspectiveIndex(aBuilder, this);
 
   if (isTransformed || useBlendMode || usingSVGEffects || useStickyPosition) {
     // We don't need to pass ancestor clipping down to our children;
@@ -2275,19 +2275,19 @@ nsIFrame::BuildDisplayListForStackingCon
    */
   if (useStickyPosition) {
     resultList.AppendNewToTop(
         new (aBuilder) nsDisplayStickyPosition(aBuilder, this, &resultList));
   }
 
   /* If we're doing VR rendering, then we need to wrap everything in a nsDisplayVR
    */
-  if (vrHMDInfo && !resultList.IsEmpty()) {
+  if (vrDeviceIndex && !resultList.IsEmpty()) {
     resultList.AppendNewToTop(
-      new (aBuilder) nsDisplayVR(aBuilder, this, &resultList, vrHMDInfo));
+      new (aBuilder) nsDisplayVR(aBuilder, this, &resultList, vrDeviceIndex));
   }
 
   /* If adding both a nsDisplayBlendContainer and a nsDisplayMixBlendMode to the
    * same list, the nsDisplayBlendContainer should be added first. This only
    * happens when the element creating this stacking context has mix-blend-mode
    * and also contains a child which has mix-blend-mode.
    * The nsDisplayBlendContainer must be added to the list first, so it does not
    * isolate the containing element blending as well.
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -72,17 +72,16 @@
 #include "nsAccessibilityService.h"
 #endif
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 PRLogModuleInfo *gVsyncLog = nullptr;
 #define VSYNC_LOG(...)  MOZ_LOG(gVsyncLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
-#define PARENT_OR_CHILD (XRE_IsParentProcess() ? "Parent" : "Child")
 
 #ifdef DEBUG
 #include "nsIObserver.h"
 
 static void debug_RegisterPrefCallbacks();
 
 #endif
 
@@ -1420,17 +1419,17 @@ VsyncChildCreateCallback::CreateVsyncAct
   RefPtr<layout::VsyncChild> child = sVsyncChildTable->GetWeak(aSourceID);
   if (!child) {
     // no, we need to construct it
     layout::PVsyncChild* actor = aPBackgroundChild->SendPVsyncConstructor(aSourceID);
     child = static_cast<layout::VsyncChild*>(actor);
     // add it to the table
     sVsyncChildTable->Put(aSourceID, child);
 
-    VSYNC_LOG("[%s]: Vsync actor %p created for display ID %s\n", PARENT_OR_CHILD, child.get(), nsIDToCString(aSourceID).get());
+    VSYNC_LOG("[%s]: Vsync actor %p created for display ID %s\n", XRE_GetProcessTypeString(), child.get(), nsIDToCString(aSourceID).get());
   }
 
   gfx::VsyncSource* display = static_cast<gfx::VsyncSource*>(child.get());
 
   aObserver->ObserveSource(display);
 }
 
 // impl for nsIWidget's generic version
@@ -1474,37 +1473,37 @@ nsBaseWidget::UpdateVsyncObserver()
 {
   // If we were already notified of shutdown, don't try to set up vsync
   if (!mShutdownObserver) {
     return;
   }
 
   if (!mIncomingVsyncObserver) {
     mIncomingVsyncObserver = new VsyncForwardingObserver();
-    VSYNC_LOG("[%s]: Widget %p creating incoming vsync observer %p\n", PARENT_OR_CHILD, this, mIncomingVsyncObserver.get());
+    VSYNC_LOG("[%s]: Widget %p creating incoming vsync observer %p\n", XRE_GetProcessTypeString(), this, mIncomingVsyncObserver.get());
 #ifdef MOZ_NUWA_PROCESS
     // After NUWA fork, make sure we set up the observer connection properly
     NuwaAddConstructor((void (*)(void *))&ForceUpdateVSyncObserver, this);
 #endif
   }
 
   if (GetTopLevelWidget() != this) {
     // if we're not the root widget, then just observe the root widget which will
     // forward to our own listeners.
     mIncomingVsyncObserver->ObserveWidget(GetTopLevelWidget());
 
-    VSYNC_LOG("[%s]: Widget %p observing root widget %p (vsync ID %s)\n", PARENT_OR_CHILD, this, GetTopLevelWidget(),
+    VSYNC_LOG("[%s]: Widget %p observing root widget %p (vsync ID %s)\n", XRE_GetProcessTypeString(), this, GetTopLevelWidget(),
               nsIDToCString(mIncomingVsyncObserver->GetObservedSourceID()).get());
 
     return;
   }
 
   mIncomingVsyncObserver->ObserveSourceID(mDesiredVsyncSourceID);
 
-  VSYNC_LOG("[%s]: Widget %p observing vsync ID %s\n", PARENT_OR_CHILD, this, nsIDToCString(mDesiredVsyncSourceID).get());
+  VSYNC_LOG("[%s]: Widget %p observing vsync ID %s\n", XRE_GetProcessTypeString(), this, nsIDToCString(mDesiredVsyncSourceID).get());
 }
 
 void
 nsBaseWidget::AddVsyncObserver(gfx::VsyncObserver *aObserver)
 {
   MOZ_ASSERT(mIncomingVsyncObserver);
   mIncomingVsyncObserver->AddVsyncObserver(aObserver);
 }
@@ -2353,16 +2352,45 @@ nsBaseWidget::UnregisterPluginWindowForR
     return;
   }
   MOZ_ASSERT(sPluginWidgetList);
   sPluginWidgetList->Remove(id);
 #endif
 }
 
 void
+nsBaseWidget::SetAttachedHMD(mozilla::gfx::VRHMDInfo* aHMD)
+{
+  VSYNC_LOG("%p SetAttachedHMD %p\n", this, aHMD);
+
+  if (GetTopLevelWidget() != this) {
+    GetTopLevelWidget()->SetAttachedHMD(aHMD);
+    return;
+  }
+
+  mHMD = aHMD;
+
+  if (aHMD) {
+    mIncomingVsyncObserver->ObserveSourceID(aHMD->GetDeviceInfo().GetVsyncSourceID());
+  } else {
+    mIncomingVsyncObserver->ObserveSourceID(mDesiredVsyncSourceID);
+  }
+}
+
+mozilla::gfx::VRHMDInfo*
+nsBaseWidget::GetAttachedHMD()
+{
+  if (GetTopLevelWidget() != this) {
+    return GetTopLevelWidget()->GetAttachedHMD();
+  }
+
+  return mHMD;
+}
+
+void
 nsBaseWidget::ParentChanged()
 {
   // Our hierarchy changed, so make sure we're still observing
   // the right widget (if we have a parent) or source (if we don't).
   if (mIncomingVsyncObserver) {
     UpdateVsyncObserver();
   }
 }
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -353,16 +353,19 @@ public:
   virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
 
   virtual bool CaptureWidgetOnScreen(RefPtr<mozilla::gfx::DrawTarget> aDT) override {
     return false;
   }
 
   virtual void StartAsyncScrollbarDrag(const AsyncDragMetrics& aDragMetrics) override;
 
+  void SetAttachedHMD(mozilla::gfx::VRHMDInfo* aHMD) override;
+  mozilla::gfx::VRHMDInfo* GetAttachedHMD() override;
+
   /**
    * Use this when GetLayerManager() returns a BasicLayerManager
    * (nsBaseWidget::GetLayerManager() does). This sets up the widget's
    * layer manager to temporarily render into aTarget.
    *
    * |aNaturalWidgetBounds| is the un-rotated bounds of |aWidget|.
    * |aRotation| is the "virtual rotation" to apply when rendering to
    * the target.  When |aRotation| is ROTATION_0,
@@ -561,16 +564,19 @@ protected:
   SizeConstraints   mSizeConstraints;
 
   bool              mUpdateCursor;
   bool              mUseAttachedEvents;
   bool              mIMEHasFocus;
 #ifdef XP_WIN
   bool              mAccessibilityInUseFlag;
 #endif
+
+  RefPtr<mozilla::gfx::VRHMDInfo> mHMD;
+
   static nsIRollupListener* gRollupListener;
 
   // the last rolled up popup. Only set this when an nsAutoRollup is in scope,
   // so it can be cleared automatically.
   static nsIContent* mLastRollup;
 
   struct InitialZoomConstraints {
     InitialZoomConstraints(const uint32_t& aPresShellID,
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -55,18 +55,18 @@ class CompositorChild;
 class LayerManager;
 class LayerManagerComposite;
 class PLayerTransactionChild;
 struct ScrollableLayerGuid;
 } // namespace layers
 namespace gfx {
 class DrawTarget;
 class SourceSurface;
+class VsyncObserver;
 class VRHMDInfo;
-class VsyncObserver;
 } // namespace gfx
 namespace widget {
 class TextEventDispatcher;
 } // namespace widget
 } // namespace mozilla
 
 /**
  * Callback function that processes events.
@@ -1740,16 +1740,22 @@ class nsIWidget : public nsISupports {
      * Implementation of SnapshotWidgetOnScreen. This is invoked by the
      * compositor for SnapshotWidgetOnScreen(), and should not be called
      * otherwise.
      */
     virtual bool CaptureWidgetOnScreen(RefPtr<mozilla::gfx::DrawTarget> aDT) = 0;
 
     virtual void StartAsyncScrollbarDrag(const AsyncDragMetrics& aDragMetrics) = 0;
 
+    /**
+     * If the widget is being displayed on a VR HMD, that HMD is accessible here.
+     */
+    virtual void SetAttachedHMD(mozilla::gfx::VRHMDInfo* aHMD) {}
+    virtual mozilla::gfx::VRHMDInfo* GetAttachedHMD() { return nullptr; }
+
 private:
   class LongTapInfo
   {
   public:
     LongTapInfo(int32_t aPointerId, ScreenIntPoint& aPoint,
                 mozilla::TimeDuration aDuration,
                 nsIObserver* aObserver) :
       mPointerId(aPointerId),