summaryrefslogtreecommitdiffstats
path: root/gfx/vr/vrhost
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/vr/vrhost/moz.build40
-rw-r--r--gfx/vr/vrhost/testhost/moz.build12
-rw-r--r--gfx/vr/vrhost/testhost/testhost.cpp43
-rw-r--r--gfx/vr/vrhost/vrhost.def15
-rw-r--r--gfx/vr/vrhost/vrhostapi.cpp374
-rw-r--r--gfx/vr/vrhost/vrhostex.h33
-rw-r--r--gfx/vr/vrhost/vrhostnightly.def23
-rw-r--r--gfx/vr/vrhost/vrhosttest.cpp370
8 files changed, 910 insertions, 0 deletions
diff --git a/gfx/vr/vrhost/moz.build b/gfx/vr/vrhost/moz.build
new file mode 100644
index 0000000000..1fa6e77f03
--- /dev/null
+++ b/gfx/vr/vrhost/moz.build
@@ -0,0 +1,40 @@
+# -*- 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/.
+
+SOURCES += ["/gfx/vr/service/VRSession.cpp", "/gfx/vr/VRShMem.cpp", "vrhostapi.cpp"]
+
+# Since .def files do not support preprocessing, switch which file is used
+# to declare exports. See comments in the files for more info.
+if CONFIG["NIGHTLY_BUILD"]:
+ DEFFILE = "vrhostnightly.def"
+ SOURCES += ["vrhosttest.cpp"]
+else:
+ DEFFILE = "vrhost.def"
+
+LOCAL_INCLUDES += [
+ "/gfx/vr",
+ "/gfx/vr/external_api",
+ "/gfx/vr/service",
+ "/ipc/chromium/src",
+]
+
+EXPORTS.vrhost = ["vrhostex.h"]
+
+DIRS += ["testhost"]
+
+# this is Windows-only for now
+DEFINES["XP_WIN"] = True
+# fixes "lld-link: error: undefined symbol: __imp_moz_xmalloc"
+DEFINES["MOZ_NO_MOZALLOC"] = True
+# fixes "STL code can only be used with infallible ::operator new()"
+DisableStlWrapping()
+
+# Define UNICODE for default support in this dll
+DEFINES["UNICODE"] = True
+DEFINES["_UNICODE"] = True
+
+# Use SharedLibrary to generate the dll
+SharedLibrary("vrhost")
diff --git a/gfx/vr/vrhost/testhost/moz.build b/gfx/vr/vrhost/testhost/moz.build
new file mode 100644
index 0000000000..5b0d3b16e1
--- /dev/null
+++ b/gfx/vr/vrhost/testhost/moz.build
@@ -0,0 +1,12 @@
+# -*- 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/.
+
+SOURCES += ["testhost.cpp"]
+
+USE_LIBS += ["vrhost"]
+
+# Use Progam to generate the executable
+Program("vrtesthost")
diff --git a/gfx/vr/vrhost/testhost/testhost.cpp b/gfx/vr/vrhost/testhost/testhost.cpp
new file mode 100644
index 0000000000..ae667aaee4
--- /dev/null
+++ b/gfx/vr/vrhost/testhost/testhost.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <windows.h>
+#include "vrhost/vrhostex.h"
+
+int main(int argc, char* argv[], char* envp[]) {
+ HINSTANCE hVR = ::LoadLibraryW(L"vrhost.dll");
+ if (hVR != nullptr) {
+ // Sometimes LoadLibrary can set an error, but, if it returns a handle,
+ // then it succeeded. Clear the last error so that subsequent calls to
+ // GetLastError don't improperly attribute failure to another API.
+ ::SetLastError(0);
+
+ PFN_SAMPLE lpfnSample = (PFN_SAMPLE)GetProcAddress(hVR, "SampleExport");
+ if (lpfnSample != nullptr) {
+ lpfnSample();
+ }
+
+ if (strcmp(argv[1], "-testsvc") == 0) {
+ PFN_SAMPLE lpfnService =
+ (PFN_SAMPLE)GetProcAddress(hVR, "TestTheService");
+
+ lpfnService();
+ } else if (strcmp(argv[1], "-testmgr") == 0) {
+ PFN_SAMPLE lpfnManager =
+ (PFN_SAMPLE)GetProcAddress(hVR, "TestTheManager");
+ lpfnManager();
+ } else if (strcmp(argv[1], "-testvrwin") == 0) {
+ PFN_SAMPLE lpfnManager =
+ (PFN_SAMPLE)GetProcAddress(hVR, "TestCreateVRWindow");
+ lpfnManager();
+ }
+
+ ::FreeLibrary(hVR);
+ hVR = nullptr;
+ }
+
+ return 0;
+}
diff --git a/gfx/vr/vrhost/vrhost.def b/gfx/vr/vrhost/vrhost.def
new file mode 100644
index 0000000000..7a80c3b247
--- /dev/null
+++ b/gfx/vr/vrhost/vrhost.def
@@ -0,0 +1,15 @@
+;+# 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/.
+
+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
+ SendVRTelemetry PRIVATE \ No newline at end of file
diff --git a/gfx/vr/vrhost/vrhostapi.cpp b/gfx/vr/vrhost/vrhostapi.cpp
new file mode 100644
index 0000000000..508e379a5f
--- /dev/null
+++ b/gfx/vr/vrhost/vrhostapi.cpp
@@ -0,0 +1,374 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// vrhostapi.cpp
+// Definition of functions that are exported from this dll
+
+#include "vrhostex.h"
+#include "VRShMem.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <random>
+#include <queue>
+
+#include "windows.h"
+
+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;
+ }
+};
+
+// VRWindowManager adds a level of indirection so that system HWND isn't exposed
+// outside of these APIs
+class VRWindowManager {
+ public:
+ 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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ uint32_t GetRandomUInt() { return randomGenerator(); }
+
+ static VRWindowManager* GetManager() {
+ if (Instance == nullptr) {
+ Instance = new VRWindowManager();
+ }
+ return Instance;
+ }
+
+ 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;
+
+class VRTelemetryManager {
+ public:
+ void SendTelemetry(uint32_t aTelemetryId, uint32_t aValue) {
+ if (!aTelemetryId) {
+ return;
+ }
+
+ mozilla::gfx::VRTelemetryState telemetryState = {0};
+ VRShmemInstance::GetInstance().PullTelemetryState(telemetryState);
+
+ if (telemetryState.uid == 0) {
+ telemetryState.uid = sUid;
+ }
+
+ switch (mozilla::gfx::VRTelemetryId(aTelemetryId)) {
+ case mozilla::gfx::VRTelemetryId::INSTALLED_FROM:
+ MOZ_ASSERT(aValue <= 0x07,
+ "VRTelemetryId::INSTALLED_FROM only allows 3 bits.");
+ telemetryState.installedFrom = true;
+ telemetryState.installedFromValue = aValue;
+ break;
+ case mozilla::gfx::VRTelemetryId::ENTRY_METHOD:
+ MOZ_ASSERT(aValue <= 0x07,
+ "VRTelemetryId::ENTRY_METHOD only allows 3 bits.");
+ telemetryState.entryMethod = true;
+ telemetryState.entryMethodValue = aValue;
+ break;
+ case mozilla::gfx::VRTelemetryId::FIRST_RUN:
+ MOZ_ASSERT(aValue <= 0x01,
+ "VRTelemetryId::FIRST_RUN only allows 1 bit.");
+ telemetryState.firstRun = true;
+ telemetryState.firstRunValue = aValue;
+ break;
+ default:
+ MOZ_CRASH("Undefined VR telemetry type.");
+ break;
+ }
+ VRShmemInstance::GetInstance().PushTelemetryState(telemetryState);
+ ++sUid;
+ }
+
+ static VRTelemetryManager* GetManager() {
+ if (Instance == nullptr) {
+ Instance = new VRTelemetryManager();
+ }
+ return Instance;
+ }
+
+ private:
+ static VRTelemetryManager* Instance;
+ static uint32_t sUid; // It starts from 1, Zero means the data is read yet
+ // from VRManager.
+};
+uint32_t VRTelemetryManager::sUid = 1;
+VRTelemetryManager* VRTelemetryManager::Instance = nullptr;
+
+// Struct to send params to StartFirefoxThreadProc
+struct StartFirefoxParams {
+ char* firefoxFolder;
+ char* firefoxProfileFolder;
+ HANDLE hProcessFx;
+};
+
+// Helper threadproc function for CreateVRWindow
+DWORD StartFirefoxThreadProc(_In_ LPVOID lpParameter) {
+ wchar_t cmd[] = L"%Sfirefox.exe -wait-for-browser -profile %S --fxr";
+
+ StartFirefoxParams* params = static_cast<StartFirefoxParams*>(lpParameter);
+ wchar_t cmdWithPath[MAX_PATH + MAX_PATH] = {0};
+ int err = swprintf_s(cmdWithPath, ARRAYSIZE(cmdWithPath), cmd,
+ params->firefoxFolder, params->firefoxProfileFolder);
+
+ if (err != -1) {
+ PROCESS_INFORMATION procFx = {0};
+ STARTUPINFO startupInfoFx = {0};
+
+#if defined(DEBUG) && defined(NIGHTLY_BUILD)
+ printf("Starting Firefox via: %S\n", cmdWithPath);
+#endif
+
+ // Start Firefox
+ bool fCreateContentProc = ::CreateProcess(nullptr, // lpApplicationName,
+ cmdWithPath,
+ nullptr, // lpProcessAttributes,
+ nullptr, // lpThreadAttributes,
+ TRUE, // bInheritHandles,
+ 0, // dwCreationFlags,
+ nullptr, // lpEnvironment,
+ nullptr, // lpCurrentDirectory,
+ &startupInfoFx, &procFx);
+
+ if (!fCreateContentProc) {
+ printf("Failed to create Firefox process");
+ }
+
+ params->hProcessFx = procFx.hProcess;
+ }
+
+ return 0;
+}
+
+// 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
+ FALSE, // bManualReset
+ FALSE, // bInitialState
+ windowState.signalName);
+
+ if (hEvent != nullptr) {
+ // Create Shmem and push state
+ 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
+ VRShmemInstance::GetInstance().PullWindowState(windowState);
+
+ (*hTex) = windowState.textureFx;
+ (*windowId) = VRWindowManager::GetManager()->SetHWND(
+ (HWND)windowState.hwndFx, fxParams.hProcessFx, hEvent);
+ (*width) = windowState.widthFx;
+ (*height) = windowState.heightFx;
+ } else {
+ // How do I failfast?
+ }
+ }
+ }
+}
+
+// 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::IME:
+ eventData1 = (uint32_t)windowState.eventState;
+ break;
+ case mozilla::gfx::VRFxEventType::FULLSCREEN:
+ eventData1 = (uint32_t)windowState.eventState;
+ break;
+ case mozilla::gfx::VRFxEventType::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);
+ if (hwnd != nullptr) {
+ switch (msg) {
+ case WM_MOUSEWHEEL:
+ // For MOUSEWHEEL, the coordinates are supposed to be at Screen origin
+ // rather than window client origin.
+ // Make the conversion to screen coordinates before posting the message
+ // to the Fx window.
+ POINT pt;
+ POINTSTOPOINT(pt, MAKEPOINTS(lparam));
+ if (!::ClientToScreen(hwnd, &pt)) {
+ break;
+ }
+ // otherwise, fallthrough
+ lparam = POINTTOPOINTS(pt);
+ case WM_MOUSEMOVE:
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_CHAR:
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ ::PostMessage(hwnd, msg, wparam, lparam);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void SendVRTelemetry(uint32_t nVRWindowID, uint32_t telemetryId,
+ uint32_t value) {
+ HWND hwnd = VRWindowManager::GetManager()->GetHWND(nVRWindowID);
+ if (hwnd == nullptr) {
+ return;
+ }
+ VRTelemetryManager::GetManager()->SendTelemetry(telemetryId, value);
+} \ No newline at end of file
diff --git a/gfx/vr/vrhost/vrhostex.h b/gfx/vr/vrhost/vrhostex.h
new file mode 100644
index 0000000000..70f4f98fba
--- /dev/null
+++ b/gfx/vr/vrhost/vrhostex.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// vrhostex.h
+// This file declares the functions and their typedefs for functions exported
+// by vrhost.dll
+
+#pragma once
+#include <stdint.h>
+
+// void SampleExport();
+typedef void (*PFN_SAMPLE)();
+
+typedef void (*PFN_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);
+
+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);
+
+typedef void (*PFN_SENDVRTELEMETRY)(uint32_t nVRWindowID, uint32_t telemetryId,
+ uint32_t value);
diff --git a/gfx/vr/vrhost/vrhostnightly.def b/gfx/vr/vrhost/vrhostnightly.def
new file mode 100644
index 0000000000..0a849e37ce
--- /dev/null
+++ b/gfx/vr/vrhost/vrhostnightly.def
@@ -0,0 +1,23 @@
+;+# 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/.
+
+LIBRARY vrhost.dll
+
+;+= Note: this file defines exports that are only available in Nightly builds
+;+= 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
+ SendVRTelemetry PRIVATE
+
+;+= Exports only available in Nightlies for testing
+ SampleExport PRIVATE
+ TestTheManager PRIVATE
+ TestTheService PRIVATE
+ TestCreateVRWindow PRIVATE \ No newline at end of file
diff --git a/gfx/vr/vrhost/vrhosttest.cpp b/gfx/vr/vrhost/vrhosttest.cpp
new file mode 100644
index 0000000000..84c11c10d6
--- /dev/null
+++ b/gfx/vr/vrhost/vrhosttest.cpp
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// vrhosttest.cpp
+// Definition of testing and validation functions that are exported from this
+// dll
+
+#include "vrhostex.h"
+#include "VRShMem.h"
+
+#include <stdio.h>
+#include "windows.h"
+
+static const char s_pszSharedEvent[] = "vrhost_test_event_signal";
+static const DWORD s_dwWFSO_WAIT = 20000;
+
+void SampleExport() { printf("vrhost.cpp hello world\n"); }
+
+// For testing ShMem as Manager and Service:
+// The two processes should output the steps, synchronously, to validate
+// 2-way communication via VRShMem as follows
+// 01 mgr: create mgr
+// 02 mgr: wait for signal
+// 03 svc: create svc
+// 04 svc: send signal
+// 05 svc: wait for signal
+// 06 mgr: push browser
+// 07 mgr: send signal
+// 08 mgr: wait for signal
+// 09 svc: pull browser
+// 10 svc: verify data
+// 11 svc: push system
+// 12 svc: send signal
+// 13 svc: wait for signal
+// 14 mgr: pull system
+// 15 mgr: verify data
+// 16 mgr: push window
+// 17 mgr: send signal
+// 18 mgr: wait for signal
+// 19 svc: pull window
+// 20 svc: verify data
+// 21 svc: push window
+// 22 svc: send signal
+// 23 mgr: pull window
+// 24 mgr: verify data
+// 25 return
+// These tests can be run with two instances of vrtesthost.exe, one first
+// running with -testmgr and the second running with -testsvc.
+// TODO: Bug 1563235 - Convert vrtesthost.exe tests into unit tests
+
+// For testing VRShMem as the Manager (i.e., the one who creates the
+// shmem). The sequence of how it tests with the service is listed above.
+void TestTheManager() {
+ printf("TestTheManager Start\n");
+
+ MOZ_ASSERT(GetLastError() == 0,
+ "TestTheManager should start with no OS errors");
+ HANDLE hEvent = ::CreateEventA(nullptr, // lpEventAttributes
+ FALSE, // bManualReset
+ FALSE, // bInitialState
+ s_pszSharedEvent // lpName
+ );
+
+ printf("\n01 mgr: create mgr\n");
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.CreateShMem(true /*aCreateOnSharedMemory*/);
+
+ printf("02 mgr: wait for signal\n");
+ ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT);
+
+ // Set some state to verify on the other side
+ mozilla::gfx::VRBrowserState browserState = {0};
+ browserState.presentationActive = true;
+ browserState.layerState[0].type =
+ mozilla::gfx::VRLayerType::LayerType_2D_Content;
+ browserState.hapticState[0].controllerIndex = 987;
+
+ printf("06 mgr: push browser\n");
+ shmem.PushBrowserState(browserState, true);
+
+ printf("07 mgr: send signal\n");
+ ::SetEvent(hEvent);
+
+ printf("08 mgr: wait for signal\n");
+ ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT);
+
+ printf("14 mgr: pull system\n");
+ mozilla::gfx::VRSystemState state;
+ shmem.PullSystemState(state.displayState, state.sensorState,
+ state.controllerState, state.enumerationCompleted,
+ nullptr);
+
+ printf(
+ "15 mgr: verify data\n"
+ "\tstate.enumerationCompleted = %d\n"
+ "\tstate.displayState.displayName = \"%s\"\n"
+ "\tstate.controllerState[1].hand = %hhu\n"
+ "\tstate.sensorState.inputFrameID = %llu\n",
+ state.enumerationCompleted, state.displayState.displayName,
+ state.controllerState[1].hand, state.sensorState.inputFrameID);
+
+ // Test the WindowState functions as the host
+ mozilla::gfx::VRWindowState windowState = {0};
+ strcpy(windowState.signalName, "randomsignalstring");
+ windowState.dxgiAdapterHost = 99;
+ windowState.heightHost = 42;
+ windowState.widthHost = 24;
+
+ printf("16 mgr: push window\n");
+ shmem.PushWindowState(windowState);
+
+ printf("17 mgr: send signal\n");
+ ::SetEvent(hEvent);
+
+ printf("18 mgr: wait for signal\n");
+ ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT);
+
+ printf("23 mgr: pull window\n");
+ shmem.PullWindowState(windowState);
+
+ printf(
+ "24 svc: verify data\n"
+ "\tstate.hwndFx = 0x%llX\n"
+ "\tstate.heightFx = %d\n"
+ "\tstate.widthFx = %d\n"
+ "\tstate.textureHandle = %p\n",
+ windowState.hwndFx, windowState.heightFx, windowState.widthFx,
+ windowState.textureFx);
+
+ shmem.CloseShMem();
+
+ printf("TestTheManager complete");
+ fflush(nullptr);
+}
+
+// For testing VRShMem as the Service (i.e., the one who consumes the
+// shmem). The sequence of how it tests with the service is listed above.
+void TestTheService() {
+ printf("TestTheService Start\n");
+
+ MOZ_ASSERT(GetLastError() == 0,
+ "TestTheService should start with no OS errors");
+ // Handle created by TestTheManager above.
+ HANDLE hEvent = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess
+ FALSE, // bInheritHandle
+ s_pszSharedEvent // lpName
+ );
+
+ printf("\n03 svc: create svc\n");
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.JoinShMem();
+
+ printf("04 svc: send signal\n");
+ ::SetEvent(hEvent);
+
+ printf("05 svc: wait for signal\n");
+ ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT);
+
+ printf("09 svc: pull browser\n");
+ mozilla::gfx::VRBrowserState state;
+ shmem.PullBrowserState(state);
+
+ printf(
+ "10 svc: verify data\n"
+ "\tstate.presentationActive = %d\n"
+ "\tstate.layerState[0].type = %hu\n"
+ "\tstate.hapticState[0].controllerIndex = %d\n",
+ state.presentationActive, state.layerState[0].type,
+ state.hapticState[0].controllerIndex);
+
+ // Set some state to verify on the other side
+ mozilla::gfx::VRSystemState systemState;
+ systemState.enumerationCompleted = true;
+ strncpy(systemState.displayState.displayName, "test from vrservice shmem",
+ mozilla::gfx::kVRDisplayNameMaxLen);
+ systemState.controllerState[1].hand = mozilla::gfx::ControllerHand::Left;
+ systemState.sensorState.inputFrameID = 1234567;
+
+ printf("11 svc: push system\n");
+ shmem.PushSystemState(systemState);
+
+ printf("12 svc: send signal\n");
+ ::SetEvent(hEvent);
+
+ printf("13 svc: wait for signal\n");
+ ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT);
+
+ // Test the WindowState functions as Firefox
+ printf("19 svc: pull window\n");
+ mozilla::gfx::VRWindowState windowState;
+ shmem.PullWindowState(windowState);
+
+ printf(
+ "20 svc: verify data\n"
+ "\tstate.signalName = \"%s\"\n"
+ "\tstate.dxgiAdapterHost = %d\n"
+ "\tstate.heightHost = %d\n"
+ "\tstate.widthHost = %d\n",
+ windowState.signalName, windowState.dxgiAdapterHost,
+ windowState.heightHost, windowState.widthHost);
+
+ windowState.hwndFx = 0x1234;
+ windowState.heightFx = 1234;
+ windowState.widthFx = 4321;
+ windowState.textureFx = (HANDLE)0x77777;
+
+ printf("21 svc: push window\n");
+ shmem.PushWindowState(windowState);
+
+ printf("22 svc: send signal\n");
+ ::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 =
+ ::GetCurrentDirectoryA(ARRAYSIZE(currentDir), currentDir);
+ currentDir[currentDirLength] = '\\';
+
+ int err = sprintf_s(currentDirProfile, ARRAYSIZE(currentDirProfile),
+ "%svrhosttest-profile", currentDir);
+ if (err > 0) {
+ printf("Starting Firefox from %s\n", currentDir);
+
+ 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
+#ifdef DEBUG
+ ::Sleep(5000);
+#else
+ ::Sleep(2000);
+#endif
+
+ printf(
+ "1. Simulating a click on the Home button, which should look "
+ "pressed\n");
+ POINT pt;
+ pt.x = 180;
+ pt.y = 700;
+ fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt));
+ ::Sleep(3000);
+ fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt));
+
+ printf(
+ "2. 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(
+ "3. Simulating clicking inside the URL bar, which should "
+ "highlight the text\n");
+ pt.x = 700;
+ pt.y = 700;
+ fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt));
+ fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt));
+
+ ::Sleep(3000);
+
+ printf("4. Type some UTF16 characters in the URL bar\n");
+ fnSendMsg(windowId, WM_CHAR, 0x4E64, 0);
+ fnSendMsg(windowId, WM_CHAR, 0x312D, 0);
+ fnSendMsg(windowId, WM_CHAR, 0x0BB9, 0);
+ fnSendMsg(windowId, WM_CHAR, 0x2745, 0);
+
+ printf(
+ "5. Simulating clicking outside the URL bar, which should "
+ "send a keyboard blur event\n");
+ pt.x = 20;
+ pt.y = 20;
+ fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt));
+ fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt));
+
+ ::Sleep(1000);
+
+ printf("6. Simulating scrolling the web content down and then up\n");
+
+ pt.x = 100;
+ pt.y = 100;
+ for (int i = -20; i < 10; ++i) {
+ fnSendMsg(windowId, WM_MOUSEWHEEL,
+ MAKELONG(0, (i < 0) ? -WHEEL_DELTA : WHEEL_DELTA),
+ POINTTOPOINTS(pt));
+ ::Sleep(100);
+ }
+
+ ::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);
+ printf("\n***Note: profile folder created at %s***\n", currentDirProfile);
+ }
+}