Bug 1581855:Part 2 - Present VR output to VR Host r=kip,jrmuizel,sotaro,bryce
☠☠ backed out by 4380c0fcda8b ☠ ☠
authorthomasmo <thomasmo@mozilla.com>
Thu, 26 Sep 2019 01:18:58 +0000
changeset 495036 70f93c9956fc4b4ffc513b610804c91fc4b5be6f
parent 495035 e5af40c83e2f41975a17f4fb12a69e71379bfb1f
child 495037 ffcbc621a3be37f38229fc079eb76ee455eabf9d
push id36621
push userdluca@mozilla.com
push dateThu, 26 Sep 2019 09:42:00 +0000
treeherdermozilla-central@1dc1a755079a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskip, jrmuizel, sotaro, bryce
bugs1581855, 1570128, 1581881
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1581855:Part 2 - Present VR output to VR Host r=kip,jrmuizel,sotaro,bryce This change is a continuation of Part 1 (Bug 1570128), where the 2D content rendered by Firefox for Firefox Reality on Desktop is marshalled through VRHost so that it can be presented in a VR environment. A new class, FxrOutputHandler, is created to manage creating a sharable texture, sharing it through VRShMem, and updating it when content updates. This class updates content with both WebRender and conventional rendering output. This initial iteration of FxrOutputHandler does not have synchronization between reading and writing this shared texture across processes. A subsequent fix (Bug 1581881) is pending, which will reuse WebVR code to manage writing to and reading from a pool of textures. This also presents issues with rendering protected media, so an additional class, FxrWindowManager, is created to manage all windows created for Firefox Reality on Desktop so that it can inform whether or not protected media can be presented. The automated manual tests in vrhosttest.cpp now show the real shared texture handle rather than a fake value, which shows that marshaling succeeded. Differential Revision: https://phabricator.services.mozilla.com/D46179
dom/ipc/BrowserChild.cpp
dom/ipc/BrowserChild.h
dom/ipc/BrowserParent.cpp
dom/ipc/BrowserParent.h
dom/ipc/PBrowser.ipdl
dom/media/eme/MediaKeySystemAccessManager.cpp
dom/media/eme/MediaKeySystemAccessManager.h
gfx/layers/d3d11/MLGDeviceD3D11.cpp
gfx/vr/FxROutputHandler.cpp
gfx/vr/FxROutputHandler.h
gfx/vr/FxRWindowManager.cpp
gfx/vr/FxRWindowManager.h
gfx/vr/moz.build
gfx/vr/nsFxrCommandLineHandler.cpp
gfx/webrender_bindings/RenderCompositorANGLE.cpp
widget/windows/WinCompositorWidget.cpp
widget/windows/WinCompositorWidget.h
widget/windows/nsWindowGfx.cpp
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -390,17 +390,23 @@ BrowserChild::BrowserChild(ContentChild*
       mRenderLayers(true),
       mPendingDocShellIsActive(false),
       mPendingDocShellReceivedMessage(false),
       mPendingRenderLayers(false),
       mPendingRenderLayersReceivedMessage(false),
       mPendingLayersObserverEpoch{0},
       mPendingDocShellBlockers(0),
       mCancelContentJSEpoch(0),
-      mWidgetNativeData(0) {
+      mWidgetNativeData(0)
+#ifdef XP_WIN
+      ,
+      mWindowSupportsProtectedMedia(true),
+      mWindowSupportsProtectedMediaChecked(false)
+#endif
+{
   mozilla::HoldJSObjects(this);
 
   nsWeakPtr weakPtrThis(do_GetWeakReference(
       static_cast<nsIBrowserChild*>(this)));  // for capture by the lambda
   mSetAllowedTouchBehaviorCallback =
       [weakPtrThis](uint64_t aInputBlockId,
                     const nsTArray<TouchBehaviorFlags>& aFlags) {
         if (nsCOMPtr<nsIBrowserChild> browserChild =
@@ -595,17 +601,18 @@ nsresult BrowserChild::Init(mozIDOMWindo
 
   mIPCOpen = true;
 
   // Recording/replaying processes use their own compositor.
   if (recordreplay::IsRecordingOrReplaying()) {
     mPuppetWidget->CreateCompositor();
   }
 
-#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && !defined(MOZ_SUITE)
+#if !defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_THUNDERBIRD) && \
+    !defined(MOZ_SUITE)
   mSessionStoreListener = new TabListener(docShell, nullptr);
   rv = mSessionStoreListener->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 #endif
   return NS_OK;
 }
 
 void BrowserChild::NotifyTabContextUpdated(bool aIsPreallocated) {
@@ -3889,16 +3896,37 @@ bool BrowserChild::UpdateSessionStore(ui
 
   Unused << SendSessionStoreUpdate(
       docShellCaps, privatedMode, positions, positionDescendants, inputs,
       idVals, xPathVals, origins, keys, values, isFullStorage, aFlushId,
       aIsFinal, mSessionStoreListener->GetEpoch());
   return true;
 }
 
+#ifdef XP_WIN
+// Cache the response to the IPC call to IsWindowSupportingProtectedMedia,
+// since it will not change for the lifetime of this object
+void BrowserChild::UpdateIsWindowSupportingProtectedMedia(bool aIsSupported) {
+  mWindowSupportsProtectedMediaChecked = true;
+  mWindowSupportsProtectedMedia = aIsSupported;
+}
+
+// Reuse the cached response to the IPC call IsWindowSupportingProtectedMedia
+// when available
+bool BrowserChild::RequiresIsWindowSupportingProtectedMediaCheck(
+    bool& aIsSupported) {
+  if (mWindowSupportsProtectedMediaChecked) {
+    aIsSupported = mWindowSupportsProtectedMedia;
+    return false;
+  } else {
+    return true;
+  }
+}
+#endif
+
 BrowserChildMessageManager::BrowserChildMessageManager(
     BrowserChild* aBrowserChild)
     : ContentFrameMessageManager(new nsFrameMessageManager(aBrowserChild)),
       mBrowserChild(aBrowserChild) {}
 
 BrowserChildMessageManager::~BrowserChildMessageManager() {}
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BrowserChildMessageManager)
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -658,16 +658,21 @@ class BrowserChild final : public nsMess
   // returns true.
   static const nsTHashtable<nsPtrHashKey<BrowserChild>>& GetVisibleTabs() {
     MOZ_ASSERT(HasVisibleTabs());
     return *sVisibleTabs;
   }
 
   bool UpdateSessionStore(uint32_t aFlushId, bool aIsFinal = false);
 
+#ifdef XP_WIN
+  void UpdateIsWindowSupportingProtectedMedia(bool aIsSupported);
+  bool RequiresIsWindowSupportingProtectedMediaCheck(bool& aIsSupported);
+#endif
+
  protected:
   virtual ~BrowserChild();
 
   mozilla::ipc::IPCResult RecvDestroy();
 
   mozilla::ipc::IPCResult RecvSetDocShellIsActive(const bool& aIsActive);
 
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
@@ -905,16 +910,21 @@ class BrowserChild final : public nsMess
   uint32_t mPendingDocShellBlockers;
   int32_t mCancelContentJSEpoch;
 
   WindowsHandle mWidgetNativeData;
 
   Maybe<LayoutDeviceToLayoutDeviceMatrix4x4> mChildToParentConversionMatrix;
   ScreenRect mRemoteDocumentRect;
 
+#ifdef XP_WIN
+  bool mWindowSupportsProtectedMedia;
+  bool mWindowSupportsProtectedMediaChecked;
+#endif
+
   // This state is used to keep track of the current visible tabs (the ones
   // rendering layers). There may be more than one if there are multiple browser
   // windows open, or tabs are being warmed up. There may be none if this
   // process does not host any visible or warming tabs.
   static nsTHashtable<nsPtrHashKey<BrowserChild>>* sVisibleTabs;
 
   DISALLOW_EVIL_CONSTRUCTORS(BrowserChild);
 };
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -113,16 +113,17 @@
 #include "IHistory.h"
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/dom/CanonicalBrowsingContext.h"
 #include "MMPrinter.h"
 #include "SessionStoreFunctions.h"
 
 #ifdef XP_WIN
 #  include "mozilla/plugins/PluginWidgetParent.h"
+#  include "FxRWindowManager.h"
 #endif
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #  include "mozilla/a11y/AccessibleWrap.h"
 #  include "mozilla/a11y/Compatibility.h"
 #  include "mozilla/a11y/nsWinUtils.h"
 #endif
 
@@ -3974,10 +3975,24 @@ void BrowserParent::OnSubFrameCrashed() 
 
   MOZ_DIAGNOSTIC_ASSERT(!mBrowsingContext->GetChildren().Length());
   // Tell the browser bridge to show the subframe crashed page.
   if (GetBrowserBridgeParent()) {
     Unused << GetBrowserBridgeParent()->SendSubFrameCrashed(mBrowsingContext);
   }
 }
 
+mozilla::ipc::IPCResult BrowserParent::RecvIsWindowSupportingProtectedMedia(
+    const uint64_t& aOuterWindowID,
+    IsWindowSupportingProtectedMediaResolver&& aResolve) {
+#ifdef XP_WIN
+  bool isFxrWindow =
+      FxRWindowManager::GetInstance()->IsFxRWindow(aOuterWindowID);
+  aResolve(!isFxrWindow);
+#else
+  MOZ_CRASH("Should only be called on Windows");
+#endif
+
+  return IPC_OK();
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -495,16 +495,20 @@ class BrowserParent final : public PBrow
       BrowsingContext* aBrowsingContext, const uint32_t& aChromeFlags,
       const TabId& aTabId);
 
   virtual mozilla::ipc::IPCResult RecvPBrowserBridgeConstructor(
       PBrowserBridgeParent* aActor, const nsString& aPresentationURL,
       const nsString& aRemoteType, BrowsingContext* aBrowsingContext,
       const uint32_t& aChromeFlags, const TabId& aTabId) override;
 
+  mozilla::ipc::IPCResult RecvIsWindowSupportingProtectedMedia(
+      const uint64_t& aOuterWindowID,
+      IsWindowSupportingProtectedMediaResolver&& aResolve);
+
   void LoadURL(nsIURI* aURI);
 
   void ResumeLoad(uint64_t aPendingSwitchID);
 
   void InitRendering();
   bool AttachLayerManager();
   void MaybeShowFrame();
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -1026,17 +1026,23 @@ child:
     async SetWidgetNativeData(WindowsHandle aHandle);
 
     /**
      * Requests the content blocking log, which is sent back in response.
      */
     async GetContentBlockingLog() returns(IPCStream ipcStream, bool success);
 
     async SkipBrowsingContextDetach() returns (bool success);
+
 parent:
+    /**
+     * Fetches whether this window supports protected media, which is sent back in response.
+     */
+    async IsWindowSupportingProtectedMedia(uint64_t aOuterWindowID) returns(bool isSupported);
+
     /** Records a history visit. */
     async VisitURI(URIParams aURI, URIParams? aLastVisitedURI,
                    uint32_t aFlags);
 
     /** Fetches the visited status for an array of URIs (Android-only). */
     async QueryVisitedState(URIParams[] aURIs);
 
     /**
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -17,16 +17,17 @@
 #ifdef XP_MACOSX
 #  include "nsCocoaFeatures.h"
 #endif
 #include "nsPrintfCString.h"
 #include "nsContentUtils.h"
 #include "nsIScriptError.h"
 #include "mozilla/Unused.h"
 #include "nsDataHashtable.h"
+#include "mozilla/dom/BrowserChild.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END
@@ -67,16 +68,61 @@ void MediaKeySystemAccessManager::Reques
 }
 
 void MediaKeySystemAccessManager::Request(
     DetailedPromise* aPromise, const nsAString& aKeySystem,
     const Sequence<MediaKeySystemConfiguration>& aConfigs, RequestType aType) {
   EME_LOG("MediaKeySystemAccessManager::Request %s",
           NS_ConvertUTF16toUTF8(aKeySystem).get());
 
+  bool isSupported = true;
+#ifdef XP_WIN
+  // In Windows OS, some Firefox windows that host content cannot support
+  // protected content, so check the status of support for this window.
+  RefPtr<BrowserChild> browser(BrowserChild::GetFrom(mWindow));
+  if (browser->RequiresIsWindowSupportingProtectedMediaCheck(isSupported)) {
+    int browserID = browser->ChromeOuterWindowID();
+
+    RefPtr<MediaKeySystemAccessManager> self(this);
+    nsString keySystem(aKeySystem);
+    RefPtr<DetailedPromise> promise(aPromise);
+    Sequence<MediaKeySystemConfiguration> configs(aConfigs);
+
+    browser->SendIsWindowSupportingProtectedMedia(browserID)->Then(
+        GetCurrentThreadSerialEventTarget(), __func__,
+        [self, browser, promise, keySystem, configs,
+         aType](bool isSupportedLambda) {
+          browser->UpdateIsWindowSupportingProtectedMedia(isSupportedLambda);
+          self->RequestCallback(isSupportedLambda, promise, keySystem, configs,
+                                aType);
+        },
+        [](const mozilla::ipc::ResponseRejectReason) {
+          MOZ_CRASH(
+              "Failed to make IPC call to IsWindowSupportingProtectedMedia");
+        });
+  } else {
+#endif
+    RequestCallback(isSupported, aPromise, aKeySystem, aConfigs, aType);
+#ifdef XP_WIN
+  }
+#endif
+}
+
+void MediaKeySystemAccessManager::RequestCallback(
+    bool aIsSupportedInWindow, DetailedPromise* aPromise,
+    const nsAString& aKeySystem,
+    const Sequence<MediaKeySystemConfiguration>& aConfigs, RequestType aType) {
+  if (!aIsSupportedInWindow) {
+    aPromise->MaybeReject(
+        NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+        NS_LITERAL_CSTRING("EME is not supported in this window"));
+
+    return;
+  }
+
   if (aKeySystem.IsEmpty()) {
     aPromise->MaybeRejectWithTypeError(u"Key system string is empty");
     // Don't notify DecoderDoctor, as there's nothing we or the user can
     // do to fix this situation; the site is using the API wrong.
     return;
   }
   if (aConfigs.IsEmpty()) {
     aPromise->MaybeRejectWithTypeError(
--- a/dom/media/eme/MediaKeySystemAccessManager.h
+++ b/dom/media/eme/MediaKeySystemAccessManager.h
@@ -48,16 +48,21 @@ class MediaKeySystemAccessManager final 
 
  private:
   enum RequestType { Initial, Subsequent };
 
   void Request(DetailedPromise* aPromise, const nsAString& aKeySystem,
                const Sequence<MediaKeySystemConfiguration>& aConfig,
                RequestType aType);
 
+  void RequestCallback(bool aIsSupportedInWindow, DetailedPromise* aPromise,
+                       const nsAString& aKeySystem,
+                       const Sequence<MediaKeySystemConfiguration>& aConfigs,
+                       RequestType aType);
+
   ~MediaKeySystemAccessManager();
 
   bool EnsureObserversAdded();
 
   bool AwaitInstall(DetailedPromise* aPromise, const nsAString& aKeySystem,
                     const Sequence<MediaKeySystemConfiguration>& aConfig);
 
   void RetryRequest(PendingRequest& aRequest);
--- a/gfx/layers/d3d11/MLGDeviceD3D11.cpp
+++ b/gfx/layers/d3d11/MLGDeviceD3D11.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/layers/UtilityMLGPU.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "mozilla/widget/WinCompositorWidget.h"
 #include "MLGShaders.h"
 #include "LayersLogging.h"
 #include "TextureD3D11.h"
 #include "gfxConfig.h"
 #include "mozilla/StaticPrefs_layers.h"
+#include "FxROutputHandler.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::gfx;
 using namespace mozilla::widget;
 using namespace mozilla::layers::mlg;
 
@@ -410,16 +411,27 @@ IntSize MLGSwapChainD3D11::GetSize() con
 
 void MLGSwapChainD3D11::Present() {
   MOZ_ASSERT(!mBackBufferInvalid.IsEmpty());
   MOZ_ASSERT(mBackBufferInvalid.GetNumRects() > 0);
 
   // See bug 1260611 comment #28 for why we do this.
   mParent->InsertPresentWaitQuery();
 
+  if (mWidget->AsWindows()->HasFxrOutputHandler()) {
+    // There is a Firefox Reality handler for this swapchain. Update this
+    // window's contents to the VR window.
+    FxROutputHandler* fxrHandler = mWidget->AsWindows()->GetFxrOutputHandler();
+    if (fxrHandler->TryInitialize(mSwapChain, mDevice)) {
+      RefPtr<ID3D11DeviceContext> context;
+      mDevice->GetImmediateContext(getter_AddRefs(context));
+      fxrHandler->UpdateOutput(context);
+    }
+  }
+
   HRESULT hr;
   if (mCanUsePartialPresents && mSwapChain1) {
     StackArray<RECT, 4> rects(mBackBufferInvalid.GetNumRects());
     size_t i = 0;
     for (auto iter = mBackBufferInvalid.RectIter(); !iter.Done(); iter.Next()) {
       const IntRect& rect = iter.Get();
       rects[i].left = rect.X();
       rects[i].top = rect.Y();
new file mode 100644
--- /dev/null
+++ b/gfx/vr/FxROutputHandler.cpp
@@ -0,0 +1,96 @@
+/* -*- 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 "FxROutputHandler.h"
+#include "mozilla/Assertions.h"
+#include "moz_external_vr.h"
+#include "VRShMem.h"
+
+// TryInitialize is responsible for associating this output handler with the
+// calling window's swapchain for subsequent updates. This also creates a
+// texture that can be shared across processes and updates VRShMem with the
+// shared texture handle.
+// See nsFxrCommandLineHandler::Handle for more information about the
+// bootstrap process.
+bool FxROutputHandler::TryInitialize(IDXGISwapChain* aSwapChain,
+                                     ID3D11Device* aDevice) {
+  if (mSwapChain == nullptr) {
+    RefPtr<ID3D11Texture2D> texOrig = nullptr;
+    HRESULT hr =
+        aSwapChain->GetBuffer(0, __uuidof(&texOrig), getter_AddRefs(texOrig));
+    if (hr != S_OK) {
+      return false;
+    }
+
+    // Create shareable texture, which will be copied to
+    D3D11_TEXTURE2D_DESC descOrig = {0};
+    texOrig->GetDesc(&descOrig);
+    descOrig.MiscFlags |= D3D11_RESOURCE_MISC_SHARED;
+    hr = aDevice->CreateTexture2D(&descOrig, nullptr,
+                                  mTexCopy.StartAssignment());
+    if (hr != S_OK) {
+      return false;
+    }
+
+    // Now, share the texture to a handle that can be marshaled to another
+    // process
+    HANDLE hCopy = nullptr;
+    RefPtr<IDXGIResource> texResource;
+    hr = mTexCopy->QueryInterface(__uuidof(&texResource),
+                                  getter_AddRefs(texResource));
+    if (hr != S_OK) {
+      return false;
+    }
+
+    hr = texResource->GetSharedHandle(&hCopy);
+    if (hr != S_OK) {
+      return false;
+    }
+
+    // The texture is successfully created and shared, so cache a
+    // pointer to the swapchain to indicate this success.
+    mSwapChain = aSwapChain;
+
+    // Finally, marshal the shared texture handle via VRShMem
+    mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+    if (shmem.JoinShMem()) {
+      mozilla::gfx::VRWindowState windowState = {0};
+      shmem.PullWindowState(windowState);
+
+      // The CLH should have populated hwndFx first
+      MOZ_ASSERT(windowState.hwndFx != 0);
+      MOZ_ASSERT(windowState.textureFx == nullptr);
+
+      windowState.textureFx = (HANDLE)hCopy;
+
+      shmem.PushWindowState(windowState);
+      shmem.LeaveShMem();
+
+      // Notify the waiting host process that the data is now available
+      HANDLE hSignal = ::OpenEventA(EVENT_ALL_ACCESS,       // dwDesiredAccess
+                                    FALSE,                  // bInheritHandle
+                                    windowState.signalName  // lpName
+      );
+      ::SetEvent(hSignal);
+      ::CloseHandle(hSignal);
+    }
+  } else {
+    MOZ_ASSERT(aSwapChain == mSwapChain);
+  }
+
+  return mSwapChain != nullptr && aSwapChain == mSwapChain;
+}
+
+// Update the contents of the shared texture.
+void FxROutputHandler::UpdateOutput(ID3D11DeviceContext* aCtx) {
+  MOZ_ASSERT(mSwapChain != nullptr);
+
+  ID3D11Texture2D* texOrig = nullptr;
+  HRESULT hr = mSwapChain->GetBuffer(0, IID_PPV_ARGS(&texOrig));
+  if (hr == S_OK) {
+    aCtx->CopyResource(mTexCopy, texOrig);
+    texOrig->Release();
+  }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/FxROutputHandler.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#pragma once
+
+struct ID3D11Texture2D;
+struct IDXGISwapChain;
+struct ID3D11DeviceContext;
+struct ID3D11Device;
+
+#include <windows.h>
+#include <d3d11_1.h>
+
+#include "mozilla/RefPtr.h"
+
+// FxROutputHandler is responsible for managing resources to share a Desktop
+// browser window with a Firefox Reality VR window.
+// Note: this object is created on the Compositor thread, but its usage should
+// only be on the RenderThread.
+class FxROutputHandler final {
+ public:
+  bool TryInitialize(IDXGISwapChain* aSwapChain, ID3D11Device* aDevice);
+  void UpdateOutput(ID3D11DeviceContext* aCtx);
+
+ private:
+  RefPtr<IDXGISwapChain> mSwapChain = nullptr;
+  RefPtr<ID3D11Texture2D> mTexCopy = nullptr;
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/gfx/vr/FxRWindowManager.cpp
@@ -0,0 +1,36 @@
+/* -*- 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 "FxRWindowManager.h"
+#include "mozilla/Assertions.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ClearOnShutdown.h"
+
+static mozilla::StaticAutoPtr<FxRWindowManager> sFxrWinMgrInstance;
+
+FxRWindowManager* FxRWindowManager::GetInstance() {
+  if (sFxrWinMgrInstance == nullptr) {
+    sFxrWinMgrInstance = new FxRWindowManager();
+    ClearOnShutdown(&sFxrWinMgrInstance);
+  }
+
+  return sFxrWinMgrInstance;
+}
+
+FxRWindowManager::FxRWindowManager() : mWindow(nullptr) {}
+
+// Track this new Firefox Reality window instance
+void FxRWindowManager::AddWindow(nsPIDOMWindowOuter* aWindow) {
+  if (mWindow != nullptr) {
+    MOZ_CRASH("Only one window is supported");
+  }
+
+  mWindow = aWindow;
+}
+
+// Returns true if the window at the provided ID was created for Firefox Reality
+bool FxRWindowManager::IsFxRWindow(uint64_t aOuterWindowID) {
+  return (mWindow != nullptr) && (mWindow->WindowID() == aOuterWindowID);
+}
new file mode 100644
--- /dev/null
+++ b/gfx/vr/FxRWindowManager.h
@@ -0,0 +1,28 @@
+/* -*- 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/. */
+
+#pragma once
+#include <cstdint>
+
+class nsPIDOMWindowOuter;
+
+// FxRWindowManager is a singleton that is responsible for tracking all of
+// the top-level windows created for Firefox Reality on Desktop. Only a
+// single window is initially supported.
+class FxRWindowManager final {
+ public:
+  static FxRWindowManager* GetInstance();
+
+  void AddWindow(nsPIDOMWindowOuter* aWindow);
+  bool IsFxRWindow(uint64_t aOuterWindowID);
+
+ private:
+  FxRWindowManager();
+
+  // Only a single window is supported for tracking. Support for multiple
+  // windows will require a data structure to collect windows as they are
+  // created.
+  nsPIDOMWindowOuter* mWindow;
+};
\ No newline at end of file
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -1,16 +1,18 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'external_api/moz_external_vr.h',
+    'FxROutputHandler.h',
+    'FxRWindowManager.h',
     'gfxVR.h',
     'ipc/VRChild.h',
     'ipc/VRGPUChild.h',
     'ipc/VRGPUParent.h',
     'ipc/VRLayerChild.h',
     'ipc/VRManagerChild.h',
     'ipc/VRManagerParent.h',
     'ipc/VRMessageUtils.h',
@@ -85,16 +87,22 @@ IPDL_SOURCES = [
 if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['NIGHTLY_BUILD']:
     XPCOM_MANIFESTS += [
       'components.conf',
     ]
     SOURCES += [
       'nsFxrCommandLineHandler.cpp',
     ]
 
+if CONFIG['OS_ARCH'] == 'WINNT':
+    SOURCES += [
+      'FxROutputHandler.cpp',
+      'FxRWindowManager.cpp'
+    ]
+
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
 CFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CFLAGS += CONFIG['TK_CFLAGS']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/gfx/vr/nsFxrCommandLineHandler.cpp
+++ b/gfx/vr/nsFxrCommandLineHandler.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef XP_WIN
 #  error "nsFxrCommandLineHandler currently only supported on Windows"
 #endif
 
 #include "nsFxrCommandLineHandler.h"
+#include "FxRWindowManager.h"
 
 #include "nsICommandLine.h"
 #include "nsIWindowWatcher.h"
 #include "mozIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "mozilla/WidgetUtils.h"
 #include "nsIWidget.h"
 #include "nsServiceManagerUtils.h"
@@ -46,19 +47,22 @@ NS_IMPL_ISUPPORTS(nsFxrCommandLineHandle
 //       |                   creates new window            |
 //       |                   sets .hwndFx in VRShMem       |
 //       |                         |                       |
 //       |                         |                  After compositor and
 //       |                         |                  swapchain created,
 //       |                         |                  share texture handle to
 //       |                         |                  VRShMem and set signal
 //  CreateVRWindow returns         |                       |
-//  to host                        |                       |
+//  to host with relevant          |                       |
+//  return data from VRShMem       |                       |
+//       |                   Fx continues to run           |
+//       |                         |                  Fx continues to render
 //       |                         |                       |
-//
+//      ...                       ...                     ...
 
 NS_IMETHODIMP
 nsFxrCommandLineHandler::Handle(nsICommandLine* aCmdLine) {
   bool handleFlagRetVal = false;
   nsresult result =
       aCmdLine->HandleFlag(NS_LITERAL_STRING("fxr"), false, &handleFlagRetVal);
   if (result == NS_OK && handleFlagRetVal) {
     aCmdLine->SetPreventDefault(true);
@@ -72,40 +76,44 @@ nsFxrCommandLineHandler::Handle(nsIComma
                                 "chrome://fxr/content/fxrui.html",  // aUrl
                                 "_blank",                           // aName
                                 "chrome,dialog=no,all",             // aFeatures
                                 nullptr,  // aArguments
                                 getter_AddRefs(newWindow));
 
     MOZ_ASSERT(result == NS_OK);
 
+    nsPIDOMWindowOuter* newWindowOuter = nsPIDOMWindowOuter::From(newWindow);
+    FxRWindowManager::GetInstance()->AddWindow(newWindowOuter);
+
     // Send the window's HWND to vrhost through VRShMem
     mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
     if (shmem.JoinShMem()) {
       mozilla::gfx::VRWindowState windowState = {0};
       shmem.PullWindowState(windowState);
 
       nsCOMPtr<nsIWidget> newWidget =
-          mozilla::widget::WidgetUtils::DOMWindowToWidget(
-              nsPIDOMWindowOuter::From(newWindow));
+          mozilla::widget::WidgetUtils::DOMWindowToWidget(newWindowOuter);
       HWND hwndWidget = (HWND)newWidget->GetNativeData(NS_NATIVE_WINDOW);
 
       // The CLH should populate these members first
       MOZ_ASSERT(windowState.hwndFx == 0);
       MOZ_ASSERT(windowState.textureFx == nullptr);
       windowState.hwndFx = (uint64_t)hwndWidget;
 
       shmem.PushWindowState(windowState);
       shmem.LeaveShMem();
 
       // The GPU process will notify the host that window creation is complete
       // after output data is set in VRShMem
       newWidget->RequestFxrOutput();
     } else {
+#ifndef NIGHTLY_BUILD
       MOZ_CRASH("failed to start with --fxr");
+#endif
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFxrCommandLineHandler::GetHelpInfo(nsACString& aResult) {
--- a/gfx/webrender_bindings/RenderCompositorANGLE.cpp
+++ b/gfx/webrender_bindings/RenderCompositorANGLE.cpp
@@ -14,16 +14,17 @@
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/layers/HelpersD3D11.h"
 #include "mozilla/layers/SyncObject.h"
 #include "mozilla/webrender/DCLayerTree.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "mozilla/widget/WinCompositorWidget.h"
 #include "mozilla/WindowsVersion.h"
+#include "FxROutputHandler.h"
 
 #undef NTDDI_VERSION
 #define NTDDI_VERSION NTDDI_WIN8
 
 #include <d3d11.h>
 #include <dcomp.h>
 #include <dxgi1_2.h>
 
@@ -331,16 +332,25 @@ bool RenderCompositorANGLE::BeginFrame(l
     }
   }
   return true;
 }
 
 void RenderCompositorANGLE::EndFrame() {
   InsertPresentWaitQuery();
 
+  if (mWidget->AsWindows()->HasFxrOutputHandler()) {
+    // There is a Firefox Reality handler for this swapchain. Update this
+    // window's contents to the VR window.
+    FxROutputHandler* fxrHandler = mWidget->AsWindows()->GetFxrOutputHandler();
+    if (fxrHandler->TryInitialize(mSwapChain, mDevice)) {
+      fxrHandler->UpdateOutput(mCtx);
+    }
+  }
+
   mSwapChain->Present(0, 0);
 }
 
 bool RenderCompositorANGLE::WaitForGPU() {
   // Note: this waits on the query we inserted in the previous frame,
   // not the one we just inserted now. Example:
   //   Insert query #1
   //   Present #1
--- a/widget/windows/WinCompositorWidget.cpp
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -333,39 +333,18 @@ void WinCompositorWidget::UpdateComposit
                       size.height,
                       SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS |
                           SWP_NOOWNERZORDER | SWP_NOZORDER)) {
     return;
   }
   mLastCompositorWndSize = size;
 }
 
-// TODO: Bug 1570128 - Forward request to swapchain
-// For now, this simply validates that data can be passed to Firefox Reality
-// host from the GPU process
+// Creates a new instance of FxROutputHandler so that this compositor widget
+// can send its output to Firefox Reality for Desktop.
 void WinCompositorWidget::RequestFxrOutput() {
-  mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
-  if (shmem.JoinShMem()) {
-    mozilla::gfx::VRWindowState windowState = {0};
-    shmem.PullWindowState(windowState);
-
-    // The CLH should have populated hwndFx first
-    MOZ_ASSERT(windowState.hwndFx != 0);
-    MOZ_ASSERT(windowState.textureFx == nullptr);
+  MOZ_ASSERT(mFxrHandler == nullptr);
 
-    windowState.textureFx = (HANDLE)0xFFFFFFFF;
-
-    shmem.PushWindowState(windowState);
-    shmem.LeaveShMem();
-
-    // Notify the waiting host process that the data is now available
-    HANDLE hSignal = ::OpenEventA(EVENT_ALL_ACCESS,       // dwDesiredAccess
-                                  FALSE,                  // bInheritHandle
-                                  windowState.signalName  // lpName
-    );
-
-    ::SetEvent(hSignal);
-    ::CloseHandle(hSignal);
-  }
+  mFxrHandler.reset(new FxROutputHandler());
 }
 
 }  // namespace widget
 }  // namespace mozilla
--- a/widget/windows/WinCompositorWidget.h
+++ b/widget/windows/WinCompositorWidget.h
@@ -7,16 +7,17 @@
 #define widget_windows_WinCompositorWidget_h
 
 #include "CompositorWidget.h"
 #include "gfxASurface.h"
 #include "mozilla/gfx/CriticalSection.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/widget/WinCompositorWindowThread.h"
+#include "FxROutputHandler.h"
 #include "nsIWidget.h"
 
 class nsWindow;
 
 namespace mozilla {
 namespace widget {
 
 class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
@@ -100,16 +101,18 @@ class WinCompositorWidget : public Compo
   void DestroyCompositorWindow();
   void UpdateCompositorWndSizeIfNecessary();
 
   mozilla::Mutex& GetTransparentSurfaceLock() {
     return mTransparentSurfaceLock;
   }
 
   void RequestFxrOutput();
+  bool HasFxrOutputHandler() const { return mFxrHandler != nullptr; }
+  FxROutputHandler* GetFxrOutputHandler() const { return mFxrHandler.get(); }
 
  protected:
  private:
   HDC GetWindowSurface();
   void FreeWindowSurface(HDC dc);
 
   void CreateTransparentSurface(const gfx::IntSize& aSize);
 
@@ -128,14 +131,16 @@ class WinCompositorWidget : public Compo
   RefPtr<gfxASurface> mTransparentSurface;
   HDC mMemoryDC;
   HDC mCompositeDC;
 
   // Locked back buffer of BasicCompositor
   uint8_t* mLockedBackBufferData;
 
   bool mNotDeferEndRemoteDrawing;
+
+  UniquePtr<FxROutputHandler> mFxrHandler;
 };
 
 }  // namespace widget
 }  // namespace mozilla
 
 #endif  // widget_windows_WinCompositorWidget_h
--- a/widget/windows/nsWindowGfx.cpp
+++ b/widget/windows/nsWindowGfx.cpp
@@ -417,17 +417,16 @@ bool nsWindow::OnPaint(HDC aDC, uint32_t
 
   return result;
 }
 
 // This override of CreateCompositor is to add support for sending the IPC
 // call for RequesetFxrOutput as soon as the compositor for this widget is
 // available.
 void nsWindow::CreateCompositor() {
-  // there's no super??
   nsWindowBase::CreateCompositor();
 
   if (mRequestFxrOutputPending) {
     GetRemoteRenderer()->SendRequestFxrOutput();
   }
 }
 
 void nsWindow::RequestFxrOutput() {