Bug 1587942 - Forwarding keyboard show/hide events to VR host API. r=thomasmo,PhilipLamb,masayuki,imanol
authorDaosheng Mu <daoshengmu@gmail.com>
Fri, 18 Oct 2019 01:24:56 +0000
changeset 559421 4dbfb65c5991c35b9539b3e121a91e69657a4f05
parent 559420 93c2588d2799d630caeb38f1c834267e278972a9
child 559422 fc99c73779480bdc4f82b2cb6e91530cddd48f40
push id12177
push usercsabou@mozilla.com
push dateMon, 21 Oct 2019 14:52:16 +0000
treeherdermozilla-beta@1918a9cd33bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersthomasmo, PhilipLamb, masayuki, imanol
bugs1587942
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 1587942 - Forwarding keyboard show/hide events to VR host API. r=thomasmo,PhilipLamb,masayuki,imanol Forwarding keyboard focus/blur events to VR browser that runs at another process. We will need to set these events in VR shmem, then VR host can receive these states from other process. Differential Revision: https://phabricator.services.mozilla.com/D48903
gfx/vr/FxRWindowManager.cpp
gfx/vr/FxRWindowManager.h
gfx/vr/VRShMem.cpp
gfx/vr/VRShMem.h
gfx/vr/external_api/moz_external_vr.h
gfx/vr/moz.build
gfx/vr/vrhost/vrhost.def
gfx/vr/vrhost/vrhostapi.cpp
gfx/vr/vrhost/vrhostex.h
gfx/vr/vrhost/vrhostnightly.def
gfx/vr/vrhost/vrhosttest.cpp
widget/windows/WinIMEHandler.cpp
--- a/gfx/vr/FxRWindowManager.cpp
+++ b/gfx/vr/FxRWindowManager.cpp
@@ -3,16 +3,18 @@
  * 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"
 
+#include "nsWindow.h"
+
 static mozilla::StaticAutoPtr<FxRWindowManager> sFxrWinMgrInstance;
 
 FxRWindowManager* FxRWindowManager::GetInstance() {
   if (sFxrWinMgrInstance == nullptr) {
     sFxrWinMgrInstance = new FxRWindowManager();
     ClearOnShutdown(&sFxrWinMgrInstance);
   }
 
@@ -29,8 +31,20 @@ void FxRWindowManager::AddWindow(nsPIDOM
 
   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);
 }
+
+// Returns true if the window was created for Firefox Reality
+bool FxRWindowManager::IsFxRWindow(const nsWindow* aWindow) const {
+  return (mWindow != nullptr) &&
+         (aWindow ==
+          mozilla::widget::WidgetUtils::DOMWindowToWidget(mWindow).take());
+}
+
+uint64_t FxRWindowManager::GetWindowID() const {
+  MOZ_ASSERT(mWindow);
+  return mWindow->WindowID();
+}
\ No newline at end of file
--- a/gfx/vr/FxRWindowManager.h
+++ b/gfx/vr/FxRWindowManager.h
@@ -2,26 +2,29 @@
 /* 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;
+class nsWindow;
 
 // 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);
+  bool IsFxRWindow(const nsWindow* aWindow) const;
+  uint64_t GetWindowID() const;
 
  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;
--- a/gfx/vr/VRShMem.cpp
+++ b/gfx/vr/VRShMem.cpp
@@ -22,21 +22,16 @@
 #endif
 
 #if !defined(XP_WIN)
 #  include <unistd.h>  // for ::sleep
 #endif
 
 using namespace mozilla::gfx;
 
-// TODO: we might need to use different names for the mutexes
-// and mapped files if we have both release and nightlies
-// running at the same time? Or...what if we have multiple
-// release builds running on same machine? (Bug 1563232)
-#define SHMEM_VERSION "0.0.2"
 #ifdef XP_WIN
 static const char* kShmemName = "moz.gecko.vr_ext." SHMEM_VERSION;
 static LPCTSTR kMutexName = TEXT("mozilla::vr::ShmemMutex" SHMEM_VERSION);
 #elif defined(XP_MACOSX)
 static const char* kShmemName = "/moz.gecko.vr_ext." SHMEM_VERSION;
 #endif  //  XP_WIN
 
 #if !defined(MOZ_WIDGET_ANDROID)
@@ -644,8 +639,54 @@ void VRShMem::PullWindowState(VRWindowSt
   WaitForMutex lock(mMutex);
   status = lock.GetStatus();
   if (status) {
     memcpy((void*)&aState, (void*)&(mExternalShmem->windowState),
            sizeof(VRWindowState));
   }
 #endif  // defined(XP_WIN)
 }
+
+void VRShMem::SendIMEState(uint64_t aWindowID,
+                           mozilla::gfx::VRFxIMEState aImeState) {
+  MOZ_ASSERT(!HasExternalShmem());
+  if (JoinShMem()) {
+    mozilla::gfx::VRWindowState windowState = {0};
+    PullWindowState(windowState);
+    windowState.windowID = aWindowID;
+    windowState.eventType = mozilla::gfx::VRFxEventType::FxEvent_IME;
+    windowState.imeState = aImeState;
+    PushWindowState(windowState);
+    LeaveShMem();
+
+#if defined(XP_WIN)
+    // 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);
+#endif  // defined(XP_WIN)
+  }
+}
+
+// Note: this should be called from the VRShMem instance that created
+// the external shmem rather than joined it.
+void VRShMem::SendShutdowmState(uint64_t aWindowID) {
+  MOZ_ASSERT(HasExternalShmem());
+
+  mozilla::gfx::VRWindowState windowState = {0};
+  PullWindowState(windowState);
+  windowState.windowID = aWindowID;
+  windowState.eventType = mozilla::gfx::VRFxEventType::FxEvent_SHUTDOWN;
+  PushWindowState(windowState);
+
+#if defined(XP_WIN)
+  // 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);
+#endif  // defined(XP_WIN)
+}
--- a/gfx/vr/VRShMem.h
+++ b/gfx/vr/VRShMem.h
@@ -41,16 +41,19 @@ class VRShMem final {
       VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState,
       VRControllerState (&aControllerState)[kVRControllerMaxCount],
       bool& aEnumerationCompleted,
       const std::function<bool()>& aWaitCondition = nullptr);
 
   void PushWindowState(VRWindowState& aState);
   void PullWindowState(VRWindowState& aState);
 
+  void SendIMEState(uint64_t aWindowID, mozilla::gfx::VRFxIMEState aImeState);
+  void SendShutdowmState(uint64_t aWindowID);
+
   bool HasExternalShmem() const { return mExternalShmem != nullptr; }
   bool IsSharedExternalShmem() const { return mIsSharedExternalShmem; }
   volatile VRExternalShmem* GetExternalShmem() const;
   bool IsDisplayStateShutdown() const;
 
  private:
   bool IsCreatedOnSharedMemory() const;
 
--- a/gfx/vr/external_api/moz_external_vr.h
+++ b/gfx/vr/external_api/moz_external_vr.h
@@ -35,17 +35,25 @@ namespace mozilla {
 #ifdef MOZILLA_INTERNAL_API
 namespace dom {
 enum class GamepadHand : uint8_t;
 enum class GamepadCapabilityFlags : uint16_t;
 }  // namespace dom
 #endif  //  MOZILLA_INTERNAL_API
 namespace gfx {
 
-static const int32_t kVRExternalVersion = 9;
+// If there is any change of "SHMEM_VERSION" or "kVRExternalVersion",
+// we need to change both of them at the same time.
+
+// TODO: we might need to use different names for the mutexes
+// and mapped files if we have both release and nightlies
+// running at the same time? Or...what if we have multiple
+// release builds running on same machine? (Bug 1563232)
+#define SHMEM_VERSION "0.0.3"
+static const int32_t kVRExternalVersion = 10;
 
 // 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;
@@ -425,23 +433,35 @@ struct VRBrowserState {
 
 struct VRSystemState {
   bool enumerationCompleted;
   VRDisplayState displayState;
   VRHMDSensorState sensorState;
   VRControllerState controllerState[kVRControllerMaxCount];
 };
 
+enum class VRFxEventType : uint8_t {
+  FxEvent_NONE = 0,
+  FxEvent_IME,
+  FxEvent_SHUTDOWN,
+  FxEvent_TOTAL
+};
+
+enum class VRFxIMEState : uint8_t { Blur, Focus };
+
 // Data shared via shmem for running Firefox in a VR windowed environment
 struct VRWindowState {
   // State from Firefox
   uint64_t hwndFx;
   uint32_t widthFx;
   uint32_t heightFx;
   VRLayerTextureHandle textureFx;
+  uint32_t windowID;
+  VRFxEventType eventType;
+  VRFxIMEState imeState;
 
   // State from VRHost
   uint32_t dxgiAdapterHost;
   uint32_t widthHost;
   uint32_t heightHost;
 
   // Name of synchronization primitive to signal change to this struct
   char signalName[32];
--- a/gfx/vr/moz.build
+++ b/gfx/vr/moz.build
@@ -88,16 +88,21 @@ if CONFIG['OS_ARCH'] == 'WINNT' and CONF
     XPCOM_MANIFESTS += [
       'components.conf',
     ]
     SOURCES += [
       'nsFxrCommandLineHandler.cpp',
     ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
+    LOCAL_INCLUDES += [
+      '/layout/generic',
+      '/widget',
+      '/widget/windows'
+    ]
     SOURCES += [
       'FxROutputHandler.cpp',
       'FxRWindowManager.cpp'
     ]
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
 CFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
--- a/gfx/vr/vrhost/vrhost.def
+++ b/gfx/vr/vrhost/vrhost.def
@@ -6,8 +6,9 @@ LIBRARY        vrhost.dll
 
 ;+= Note: this file defines exports that are only available in Beta and
 ;+= Release builds so that test exports are not exposed.
 ;+= Please ensure that these exports are also declared in vrhostnightly.def.
 EXPORTS
                CreateVRWindow       PRIVATE
                CloseVRWindow        PRIVATE
                SendUIMessageToVRWindow  PRIVATE
+               WaitForVREvent       PRIVATE
--- a/gfx/vr/vrhost/vrhostapi.cpp
+++ b/gfx/vr/vrhost/vrhostapi.cpp
@@ -23,29 +23,40 @@ class VRWindowManager {
   HWND GetHWND(uint32_t nId) {
     if (nId == nWindow) {
       return hWindow;
     } else {
       return nullptr;
     }
   }
 
+  uint32_t GetId(HWND hwnd) {
+    if (hwnd == hWindow) {
+      return nWindow;
+    } else {
+      return 0;
+    }
+  }
+
   HANDLE GetProc(uint32_t nId) {
     if (nId == nWindow) {
       return hProc;
     } else {
       return nullptr;
     }
   }
 
-  uint32_t SetHWND(HWND hwnd, HANDLE hproc) {
+  HANDLE GetEvent() { return hEvent; }
+
+  uint32_t SetHWND(HWND hwnd, HANDLE hproc, HANDLE hevent) {
     if (hWindow == nullptr) {
       MOZ_ASSERT(hwnd != nullptr && hproc != nullptr);
       hWindow = hwnd;
       hProc = hproc;
+      hEvent = hevent;
       nWindow = GetRandomUInt();
 #if defined(DEBUG) && defined(NIGHTLY_BUILD)
       printf("VRWindowManager: Storing HWND: 0x%p as ID: 0x%X\n", hWindow,
              nWindow);
 #endif
       return nWindow;
     } else {
       return -1;
@@ -64,16 +75,17 @@ class VRWindowManager {
  private:
   static VRWindowManager* Instance;
 
   // For now, simply store the ID and HWND, and expand
   // to a map when multiple windows/instances are supported.
   uint32_t nWindow = 0;
   HWND hWindow = nullptr;
   HANDLE hProc = nullptr;
+  HANDLE hEvent = nullptr;
   std::random_device randomGenerator;
 };
 VRWindowManager* VRWindowManager::Instance = nullptr;
 
 // Struct to send params to StartFirefoxThreadProc
 struct StartFirefoxParams {
   char* firefoxFolder;
   char* firefoxProfileFolder;
@@ -113,87 +125,153 @@ DWORD StartFirefoxThreadProc(_In_ LPVOID
     }
 
     params->hProcessFx = procFx.hProcess;
   }
 
   return 0;
 }
 
+class VRShmemInstance {
+ public:
+  VRShmemInstance() = delete;
+  VRShmemInstance(const VRShmemInstance& aRHS) = delete;
+
+  static mozilla::gfx::VRShMem& GetInstance() {
+    static mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+    return shmem;
+  }
+};
+
 // This export is responsible for starting up a new VR window in Firefox and
 // returning data related to its creation back to the caller.
 // See nsFxrCommandLineHandler::Handle for more details about the bootstrapping
 // process with Firefox.
 void CreateVRWindow(char* firefoxFolderPath, char* firefoxProfilePath,
                     uint32_t dxgiAdapterID, uint32_t widthHost,
                     uint32_t heightHost, uint32_t* windowId, void** hTex,
                     uint32_t* width, uint32_t* height) {
   mozilla::gfx::VRWindowState windowState = {0};
 
   int err = sprintf_s(windowState.signalName, ARRAYSIZE(windowState.signalName),
                       "fxr::CreateVRWindow::%X",
                       VRWindowManager::GetManager()->GetRandomUInt());
 
   if (err > 0) {
     HANDLE hEvent = ::CreateEventA(nullptr,  // attributes
-                                   TRUE,     // bManualReset
+                                   FALSE,    // bManualReset
                                    FALSE,    // bInitialState
                                    windowState.signalName);
 
     if (hEvent != nullptr) {
       // Create Shmem and push state
-      mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
-      shmem.CreateShMem(true /*aCreateOnSharedMemory*/);
-      shmem.PushWindowState(windowState);
+      VRShmemInstance::GetInstance().CreateShMem(
+          true /*aCreateOnSharedMemory*/);
+      VRShmemInstance::GetInstance().PushWindowState(windowState);
 
       // Start Firefox in another thread so that this thread can wait for the
       // window state to be updated during Firefox startup
       StartFirefoxParams fxParams = {0};
       fxParams.firefoxFolder = firefoxFolderPath;
       fxParams.firefoxProfileFolder = firefoxProfilePath;
       DWORD dwTid = 0;
       HANDLE hThreadFx = CreateThread(nullptr, 0, StartFirefoxThreadProc,
                                       &fxParams, 0, &dwTid);
       if (hThreadFx != nullptr) {
         // Wait for Firefox to populate rest of window state
         ::WaitForSingleObject(hEvent, INFINITE);
 
         // Update local WindowState with data from Firefox
-        shmem.PullWindowState(windowState);
+        VRShmemInstance::GetInstance().PullWindowState(windowState);
 
         (*hTex) = windowState.textureFx;
         (*windowId) = VRWindowManager::GetManager()->SetHWND(
-            (HWND)windowState.hwndFx, fxParams.hProcessFx);
+            (HWND)windowState.hwndFx, fxParams.hProcessFx, hEvent);
         (*width) = windowState.widthFx;
         (*height) = windowState.heightFx;
-
-        // Neither the Shmem nor its window state are needed anymore
-        windowState = {0};
-        shmem.PushWindowState(windowState);
       } else {
         // How do I failfast?
       }
-
-      shmem.CloseShMem();
     }
   }
 }
 
+// Keep track of when WaitForVREvent is running to manage shutdown of
+// this vrhost. See CloseVRWindow for more details.
+volatile bool s_WaitingForVREvent = false;
+
+// Blocks until a new event is set on the shmem.
+// Note: this function can be called from any thread.
+void WaitForVREvent(uint32_t& nVRWindowID, uint32_t& eventType,
+                    uint32_t& eventData1, uint32_t& eventData2) {
+  MOZ_ASSERT(!s_WaitingForVREvent);
+  s_WaitingForVREvent = true;
+
+  // Initialize all of the out params
+  nVRWindowID = 0;
+  eventType = 0;
+  eventData1 = 0;
+  eventData2 = 0;
+
+  if (VRShmemInstance::GetInstance().HasExternalShmem()) {
+    HANDLE evt = VRWindowManager::GetManager()->GetEvent();
+    const DWORD waitResult = ::WaitForSingleObject(evt, INFINITE);
+    if (waitResult != WAIT_OBJECT_0) {
+      MOZ_ASSERT(false && "Error WaitForVREvent().\n");
+      return;
+    }
+    mozilla::gfx::VRWindowState windowState = {0};
+    VRShmemInstance::GetInstance().PullWindowState(windowState);
+
+    nVRWindowID =
+        VRWindowManager::GetManager()->GetId((HWND)windowState.hwndFx);
+    if (nVRWindowID != 0) {
+      eventType = (uint32_t)windowState.eventType;
+      mozilla::gfx::VRFxEventType fxEvent =
+          mozilla::gfx::VRFxEventType(eventType);
+
+      switch (fxEvent) {
+        case mozilla::gfx::VRFxEventType::FxEvent_IME:
+          eventData1 = (uint32_t)windowState.imeState;
+          break;
+        case mozilla::gfx::VRFxEventType::FxEvent_SHUTDOWN:
+          VRShmemInstance::GetInstance().CloseShMem();
+          break;
+        default:
+          MOZ_ASSERT(false && "Undefined VR Fx event.");
+          break;
+      }
+    }
+  }
+  s_WaitingForVREvent = false;
+}
+
 // Sends a message to the VR window to close.
 void CloseVRWindow(uint32_t nVRWindowID, bool waitForTerminate) {
   HWND hwnd = VRWindowManager::GetManager()->GetHWND(nVRWindowID);
   if (hwnd != nullptr) {
     ::SendMessage(hwnd, WM_CLOSE, 0, 0);
 
     if (waitForTerminate) {
       // Wait for Firefox main process to exit
       ::WaitForSingleObject(VRWindowManager::GetManager()->GetProc(nVRWindowID),
                             INFINITE);
     }
   }
+
+  // If a thread is currently blocked on WaitForVREvent, then defer closing the
+  // shmem to that thread by sending an async event.
+  // If WaitForVREvent is not running, it is safe to close the shmem now.
+  // Subsequent calls to WaitForVREvent will return early because it does not
+  // have an external shmem.
+  if (s_WaitingForVREvent) {
+    VRShmemInstance::GetInstance().SendShutdowmState(nVRWindowID);
+  } else {
+    VRShmemInstance::GetInstance().CloseShMem();
+  }
 }
 
 // Forwards Win32 UI window messages to the Firefox widget/window associated
 // with nVRWindowID. Note that not all message type is supported (only those
 // allowed in the switch block below).
 void SendUIMessageToVRWindow(uint32_t nVRWindowID, uint32_t msg,
                              uint64_t wparam, uint64_t lparam) {
   HWND hwnd = VRWindowManager::GetManager()->GetHWND(nVRWindowID);
--- a/gfx/vr/vrhost/vrhostex.h
+++ b/gfx/vr/vrhost/vrhostex.h
@@ -20,8 +20,11 @@ typedef void (*PFN_CREATEVRWINDOW)(char*
                                    uint32_t heightHost, uint32_t* windowId,
                                    void** hTex, uint32_t* width,
                                    uint32_t* height);
 
 typedef void (*PFN_CLOSEVRWINDOW)(uint32_t nVRWindowID, bool waitForTerminate);
 
 typedef void (*PFN_SENDUIMSG)(uint32_t nVRWindowID, uint32_t msg,
                               uint64_t wparam, uint64_t lparam);
+
+typedef void (*PFN_WAITFORVREVENT)(uint32_t& nVRWindowID, uint32_t& eventType,
+                                   uint32_t& eventData1, uint32_t& eventData2);
--- a/gfx/vr/vrhost/vrhostnightly.def
+++ b/gfx/vr/vrhost/vrhostnightly.def
@@ -8,14 +8,15 @@ LIBRARY        vrhost.dll
 ;+= so that test exports are not exposed in Beta and Release builds.
 ;+= Please ensure that the Public Export APIs in the first section below are
 ;+= also declared in vrhost.def.
 EXPORTS
 ;+= Public Export APIs for vrhost
                CreateVRWindow       PRIVATE
                CloseVRWindow        PRIVATE
                SendUIMessageToVRWindow  PRIVATE
+               WaitForVREvent       PRIVATE
 
 ;+= Exports only available in Nightlies for testing
                SampleExport         PRIVATE
                TestTheManager       PRIVATE
                TestTheService       PRIVATE
                TestCreateVRWindow   PRIVATE
\ No newline at end of file
--- a/gfx/vr/vrhost/vrhosttest.cpp
+++ b/gfx/vr/vrhost/vrhosttest.cpp
@@ -214,30 +214,56 @@ void TestTheService() {
   ::SetEvent(hEvent);
 
   shmem.LeaveShMem();
 
   printf("TestTheService complete");
   fflush(nullptr);
 }
 
+DWORD TestWaitForVREventThreadProc(_In_ LPVOID lpParameter) {
+  // WaitForVREvent
+  printf("\nStarting TestWaitForVREventThreadProc\n");
+
+  PFN_WAITFORVREVENT fnWaitForVRMsg = (PFN_WAITFORVREVENT)lpParameter;
+
+  uint32_t nVRWindowID = 0;
+  uint32_t eventType = 0;
+  uint32_t eventData1 = 0;
+  uint32_t eventData2 = 0;
+
+  while (eventType != 2) {  // FxEvent_SHUTDOWN
+    fnWaitForVRMsg(nVRWindowID, eventType, eventData1, eventData2);
+    printf(
+        "\nWaitForVRMessage:\n\tvrWindowID: %d\n\teventType: %d\n\teventData1: "
+        "%d\n\teventData2: %d\n",
+        nVRWindowID, eventType, eventData1, eventData2);
+  }
+
+  printf("\nReturning from TestWaitForVREventThreadProc\n");
+
+  return 0;
+}
+
 // This function tests the export CreateVRWindow by outputting the return values
 // from the call to the console, as well as testing CloseVRWindow after the data
 // is retrieved.
 void TestCreateVRWindow() {
   printf("TestCreateVRWindow Start\n");
 
   // Cache function calls to test real-world export and usage
   HMODULE hVRHost = ::GetModuleHandleA("vrhost.dll");
   PFN_CREATEVRWINDOW fnCreate =
       (PFN_CREATEVRWINDOW)::GetProcAddress(hVRHost, "CreateVRWindow");
   PFN_CLOSEVRWINDOW fnClose =
       (PFN_CLOSEVRWINDOW)::GetProcAddress(hVRHost, "CloseVRWindow");
   PFN_SENDUIMSG fnSendMsg =
       (PFN_SENDUIMSG)::GetProcAddress(hVRHost, "SendUIMessageToVRWindow");
+  PFN_WAITFORVREVENT fnWaitForVRMsg =
+      (PFN_WAITFORVREVENT)::GetProcAddress(hVRHost, "WaitForVREvent");
 
   // Create the VR Window and store data from creation
   char currentDir[MAX_PATH] = {0};
   char currentDirProfile[MAX_PATH] = {0};
   DWORD currentDirLength =
       ::GetCurrentDirectory(ARRAYSIZE(currentDir), currentDir);
   currentDir[currentDirLength] = '\\';
 
@@ -248,44 +274,69 @@ void TestCreateVRWindow() {
 
     UINT windowId;
     HANDLE hTex;
     UINT width;
     UINT height;
     fnCreate(currentDir, currentDirProfile, 0, 100, 200, &windowId, &hTex,
              &width, &height);
 
+    // Now that the Fx window is created, start a new thread to wait for VR
+    // events to be sent from it
+    DWORD dwTid;
+    HANDLE hThreadWait = CreateThread(nullptr, 0, TestWaitForVREventThreadProc,
+                                      (void*)fnWaitForVRMsg, 0, &dwTid);
+
     // Wait for Fx to finish launch
     ::Sleep(5000);
 
     printf(
         "Now, simulating a click on the Home button, which should look "
         "pressed\n");
     POINT pt;
-    pt.x = 450;
-    pt.y = 50;
+    pt.x = 180;
+    pt.y = 790;
     fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt));
     ::Sleep(3000);
     fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt));
 
     printf(
         "Next, simulating hovering across the URL bar, which should turn "
         "blue\n");
     pt.x = 600;
     for (int i = 0; i < 100; ++i) {
       pt.x++;
       fnSendMsg(windowId, WM_MOUSEMOVE, 0, POINTTOPOINTS(pt));
       ::Sleep(5);
     }
 
+    printf(
+        "Next, simulating clicking inside the URL bar, which should "
+        "highlight the text\n");
+    pt.x = 700;
+    pt.y = 790;
+    fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt));
+    fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt));
+    ::Sleep(3000);
+
+    printf(
+        "Finally, simulating clicking outside the URL bar, which should "
+        "send a keyboard blur event\n");
+    pt.x = 80;
+    fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt));
+    fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt));
+
     ::Sleep(5000);
 
     // Close the Firefox VR Window
     fnClose(windowId, true);
 
+    // Wait for the VR event thread to return
+    ::WaitForSingleObject(hThreadWait, INFINITE);
+
     // Print output from CreateVRWindow
     printf(
         "\n\nTestCreateVRWindow End:\n"
         "\twindowId = 0x%X\n"
         "\thTex = 0x%p\n"
         "\twidth = %d\n"
         "\theight = %d\n",
         windowId, hTex, width, height);
--- a/widget/windows/WinIMEHandler.cpp
+++ b/widget/windows/WinIMEHandler.cpp
@@ -27,16 +27,20 @@
 #endif  // #ifdef ACCESSIBILITY
 
 #include "shellapi.h"
 #include "shlobj.h"
 #include "powrprof.h"
 #include "setupapi.h"
 #include "cfgmgr32.h"
 
+#include "FxRWindowManager.h"
+#include "VRShMem.h"
+#include "moz_external_vr.h"
+
 const char* kOskPathPrefName = "ui.osk.on_screen_keyboard_path";
 const char* kOskEnabled = "ui.osk.enabled";
 const char* kOskDetectPhysicalKeyboard = "ui.osk.detect_physical_keyboard";
 const char* kOskRequireWin10 = "ui.osk.require_win10";
 const char* kOskDebugReason = "ui.osk.debug.keyboardDisplayReason";
 
 namespace mozilla {
 namespace widget {
@@ -732,16 +736,24 @@ void IMEHandler::SetInputScopeForIMM32(n
   if (scopes && arraySize > 0) {
     sSetInputScopes(aWindow->GetWindowHandle(), scopes, arraySize, nullptr, 0,
                     nullptr, nullptr);
   }
 }
 
 // static
 void IMEHandler::MaybeShowOnScreenKeyboard() {
+#ifdef NIGHTLY_BUILD
+  if (FxRWindowManager::GetInstance()->IsFxRWindow(sFocusedWindow)) {
+    mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+    shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(),
+                       mozilla::gfx::VRFxIMEState::Focus);
+    return;
+  }
+#endif  // NIGHTLY_BUILD
   if (sPluginHasFocus || !IsWin8OrLater() ||
       !Preferences::GetBool(kOskEnabled, true) || GetOnScreenKeyboardWindow() ||
       !IMEHandler::NeedOnScreenKeyboard()) {
     return;
   }
 
   // On Windows 10 we require tablet mode, unless the user has set the relevant
   // Windows setting to enable the on-screen keyboard in desktop mode.
@@ -754,20 +766,26 @@ void IMEHandler::MaybeShowOnScreenKeyboa
     return;
   }
 
   IMEHandler::ShowOnScreenKeyboard();
 }
 
 // static
 void IMEHandler::MaybeDismissOnScreenKeyboard(nsWindow* aWindow) {
+#ifdef NIGHTLY_BUILD
+  if (FxRWindowManager::GetInstance()->IsFxRWindow(aWindow)) {
+    mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+    shmem.SendIMEState(FxRWindowManager::GetInstance()->GetWindowID(),
+                       mozilla::gfx::VRFxIMEState::Blur);
+  }
+#endif  // NIGHTLY_BUILD
   if (sPluginHasFocus || !IsWin8OrLater()) {
     return;
   }
-
   ::PostMessage(aWindow->GetWindowHandle(), MOZ_WM_DISMISS_ONSCREEN_KEYBOARD, 0,
                 0);
 }
 
 // static
 bool IMEHandler::WStringStartsWithCaseInsensitive(const std::wstring& aHaystack,
                                                   const std::wstring& aNeedle) {
   std::wstring lowerCaseHaystack(aHaystack);