Add OpenXR to xr_browser_tests infrastructure and enable tests

This change runs most of the existing OpenVR/WMR tests with
OpenXR by adding OpenXR to the WEBXR_VR_ALL_RUNTIMES* macros.

Remaining tests to add are addressed in:
crbug.com/986637 (input)
crbug.com/986621 (drawing pixels)

Bug: 976434
Change-Id: I6b0a48d37d1380ba50b97b2d97d5b7accb9c495b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1714304
Commit-Queue: Patrick To <patrto@microsoft.com>
Reviewed-by: Brian Sheedy <bsheedy@chromium.org>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/master@{#683403}
diff --git a/chrome/browser/vr/BUILD.gn b/chrome/browser/vr/BUILD.gn
index e5cbac3c..4533f89d 100644
--- a/chrome/browser/vr/BUILD.gn
+++ b/chrome/browser/vr/BUILD.gn
@@ -775,6 +775,11 @@
       ]
 
       data_deps += [ "//device/vr:openvr_mock" ]
+
+      if (enable_openxr) {
+        deps += [ "//third_party/openxr" ]
+        data_deps += [ "//device/vr:openxr_mock" ]
+      }
     }
   }
 
diff --git a/chrome/browser/vr/test/multi_class_browser_test.h b/chrome/browser/vr/test/multi_class_browser_test.h
index 8aeb9c6..05ac882 100644
--- a/chrome/browser/vr/test/multi_class_browser_test.h
+++ b/chrome/browser/vr/test/multi_class_browser_test.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_VR_TEST_MULTI_CLASS_BROWSER_TEST_H_
 
 #include "content/public/test/browser_test.h"
+#include "device/vr/buildflags/buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // A collection of macros to automatically run a browser test multiple times
@@ -70,6 +71,15 @@
   void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
       base_class* t)
 
+#define IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(                             \
+    test_class1, test_class2, test_class3, base_class, test_name)        \
+  DEFINE_RUN_TEST_IMPL_(test_name, base_class)                           \
+  DEFINE_BROWSER_TEST_(test_class1, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class2, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class3, test_name)                           \
+  void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
+      base_class* t)
+
 #define IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F2(              \
     test_class1, test_class2, base_class, test_name)                     \
   DEFINE_RUN_TEST_IMPL_(test_name, base_class)                           \
@@ -80,19 +90,45 @@
   void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
       base_class* t)
 
+#define IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F3(              \
+    test_class1, test_class2, test_class3, base_class, test_name)        \
+  DEFINE_RUN_TEST_IMPL_(test_name, base_class)                           \
+  DEFINE_BROWSER_TEST_(test_class1, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class2, test_name)                           \
+  DEFINE_BROWSER_TEST_(test_class3, test_name)                           \
+  DEFINE_INCOGNITO_BROWSER_TEST_(test_class1, test_name)                 \
+  DEFINE_INCOGNITO_BROWSER_TEST_(test_class2, test_name)                 \
+  DEFINE_INCOGNITO_BROWSER_TEST_(test_class3, test_name)                 \
+  void MULTI_CLASS_RUNNER_NAME_(test_name)::ActuallyRunTestOnMainThread( \
+      base_class* t)
+
 // Helper macro to cut down on duplicate code since most uses of
-// IN_PROC_MULTI_CLASS_BROWSER_TEST_F2 are passed the same OpenVR and WMR
-// classes and the same base class
+// IN_PROC_MULTI_CLASS_BROWSER_TEST_F3 are passed the same OpenVR, WMR, and
+// OpenXR classes and the same base class
+#if BUILDFLAG(ENABLE_OPENXR)
+#define WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(test_name) \
+  IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(                  \
+      WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest,  \
+      WebXrVrOpenXrBrowserTest, WebXrVrBrowserTestBase, test_name)
+#else
 #define WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(test_name)         \
   IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest, \
                                       WebXrVrWmrBrowserTest,    \
                                       WebXrVrBrowserTestBase, test_name)
+#endif  // BUILDFLAG(ENABLE_OPENXR)
 
 // The same as WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F, but runs the tests in
 // incognito mode as well.
+#if BUILDFLAG(ENABLE_OPENXR)
+#define WEBXR_VR_ALL_RUNTIMES_PLUS_INCOGNITO_BROWSER_TEST_F(test_name) \
+  IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F3(                  \
+      WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest,                 \
+      WebXrVrOpenXrBrowserTest, WebXrVrBrowserTestBase, test_name)
+#else
 #define WEBXR_VR_ALL_RUNTIMES_PLUS_INCOGNITO_BROWSER_TEST_F(test_name)         \
   IN_PROC_MULTI_CLASS_PLUS_INCOGNITO_BROWSER_TEST_F2(                          \
       WebXrVrOpenVrBrowserTest, WebXrVrWmrBrowserTest, WebXrVrBrowserTestBase, \
       test_name)
+#endif  // ENABLE_OPENXR
 
 #endif  // CHROME_BROWSER_VR_TEST_MULTI_CLASS_BROWSER_TEST_H_
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.cc b/chrome/browser/vr/test/webxr_vr_browser_test.cc
index 863b159..6da016f 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.cc
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.cc
@@ -14,6 +14,10 @@
 
 namespace vr {
 
+WebXrVrBrowserTestBase::WebXrVrBrowserTestBase() {
+  enable_features_.push_back(features::kWebXr);
+}
+
 void WebXrVrBrowserTestBase::EnterSessionWithUserGesture(
     content::WebContents* web_contents) {
 #if defined(OS_WIN)
@@ -69,7 +73,26 @@
   return gfx::Vector3dF();
 }
 
+WebXrVrRuntimelessBrowserTest::WebXrVrRuntimelessBrowserTest() {
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+  disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
+WebXrVrRuntimelessBrowserTestSensorless::
+    WebXrVrRuntimelessBrowserTestSensorless() {
+  disable_features_.push_back(device::kWebXrOrientationSensorDevice);
+}
+
 #if defined(OS_WIN)
+
+WebXrVrOpenVrBrowserTestBase::WebXrVrOpenVrBrowserTestBase() {
+  enable_features_.push_back(features::kOpenVR);
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+  disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
 XrBrowserTestBase::RuntimeType WebXrVrOpenVrBrowserTestBase::GetRuntimeType()
     const {
   return XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR;
@@ -95,6 +118,56 @@
     const {
   return XrBrowserTestBase::RuntimeType::RUNTIME_WMR;
 }
+
+#if BUILDFLAG(ENABLE_OPENXR)
+
+WebXrVrOpenXrBrowserTestBase::WebXrVrOpenXrBrowserTestBase() {
+  enable_features_.push_back(features::kOpenXR);
+#if BUILDFLAG(ENABLE_WINDOWS_MR)
+  disable_features_.push_back(features::kWindowsMixedReality);
+#endif
+}
+
+WebXrVrOpenXrBrowserTestBase::~WebXrVrOpenXrBrowserTestBase() = default;
+
+XrBrowserTestBase::RuntimeType WebXrVrOpenXrBrowserTestBase::GetRuntimeType()
+    const {
+  return XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR;
+}
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
+WebXrVrOpenVrBrowserTest::WebXrVrOpenVrBrowserTest() {
+  // We know at this point that we're going to be running with both OpenVR and
+  // WebXR enabled, so enforce the DirectX 11.1 requirement.
+  runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+
+WebXrVrWmrBrowserTest::WebXrVrWmrBrowserTest() {
+  // WMR already enabled by default.
+  runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+WebXrVrOpenXrBrowserTest::WebXrVrOpenXrBrowserTest() {
+  runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
+}
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
+// Test classes with WebXR disabled.
+WebXrVrOpenVrBrowserTestWebXrDisabled::WebXrVrOpenVrBrowserTestWebXrDisabled() {
+  disable_features_.push_back(features::kWebXr);
+}
+
+WebXrVrWmrBrowserTestWebXrDisabled::WebXrVrWmrBrowserTestWebXrDisabled() {
+  disable_features_.push_back(features::kWebXr);
+}
+
+#if BUILDFLAG(ENABLE_OPENXR)
+WebXrVrOpenXrBrowserTestWebXrDisabled::WebXrVrOpenXrBrowserTestWebXrDisabled() {
+  disable_features_.push_back(features::kWebXr);
+}
+#endif  // BUIDFLAG(ENABLE_OPENXR)
+
 #endif  // OS_WIN
 
 }  // namespace vr
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.h b/chrome/browser/vr/test/webxr_vr_browser_test.h
index 71fcc1e..4c37eddd 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.h
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.h
@@ -27,7 +27,7 @@
 // WebXR for VR-specific test base class without any particular runtime.
 class WebXrVrBrowserTestBase : public WebXrBrowserTestBase {
  public:
-  WebXrVrBrowserTestBase() { enable_features_.push_back(features::kWebXr); }
+  WebXrVrBrowserTestBase();
   void EnterSessionWithUserGesture(content::WebContents* web_contents) override;
   void EnterSessionWithUserGestureOrFail(
       content::WebContents* web_contents) override;
@@ -52,11 +52,7 @@
 // Test class with OpenVR disabled.
 class WebXrVrRuntimelessBrowserTest : public WebXrVrBrowserTestBase {
  public:
-  WebXrVrRuntimelessBrowserTest() {
-#if BUILDFLAG(ENABLE_WINDOWS_MR)
-    disable_features_.push_back(features::kWindowsMixedReality);
-#endif
-  }
+  WebXrVrRuntimelessBrowserTest();
 };
 
 // WebXrOrientationSensorDevice is only defined when the enable_vr flag is set.
@@ -64,9 +60,7 @@
 class WebXrVrRuntimelessBrowserTestSensorless
     : public WebXrVrRuntimelessBrowserTest {
  public:
-  WebXrVrRuntimelessBrowserTestSensorless() {
-    disable_features_.push_back(device::kWebXrOrientationSensorDevice);
-  }
+  WebXrVrRuntimelessBrowserTestSensorless();
 };
 #endif  // BUILDFLAG(ENABLE_VR)
 
@@ -75,12 +69,7 @@
 // OpenVR-specific subclass of WebXrVrBrowserTestBase.
 class WebXrVrOpenVrBrowserTestBase : public WebXrVrBrowserTestBase {
  public:
-  WebXrVrOpenVrBrowserTestBase() {
-    enable_features_.push_back(features::kOpenVR);
-#if BUILDFLAG(ENABLE_WINDOWS_MR)
-    disable_features_.push_back(features::kWindowsMixedReality);
-#endif
-  }
+  WebXrVrOpenVrBrowserTestBase();
   XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
   gfx::Vector3dF GetControllerOffset() const override;
 };
@@ -102,39 +91,54 @@
   std::unique_ptr<MockXRDeviceHookBase> dummy_hook_;
 };
 
+#if BUILDFLAG(ENABLE_OPENXR)
+// OpenXR-specific subclass of WebXrVrBrowserTestBase.
+class WebXrVrOpenXrBrowserTestBase : public WebXrVrBrowserTestBase {
+ public:
+  WebXrVrOpenXrBrowserTestBase();
+  ~WebXrVrOpenXrBrowserTestBase() override;
+  XrBrowserTestBase::RuntimeType GetRuntimeType() const override;
+};
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
 // Test class with standard features enabled: WebXR and OpenVR.
 class WebXrVrOpenVrBrowserTest : public WebXrVrOpenVrBrowserTestBase {
  public:
-  WebXrVrOpenVrBrowserTest() {
-    // We know at this point that we're going to be running with both OpenVR and
-    // WebXR enabled, so enforce the DirectX 11.1 requirement.
-    runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
-  }
+  WebXrVrOpenVrBrowserTest();
 };
 
 class WebXrVrWmrBrowserTest : public WebXrVrWmrBrowserTestBase {
  public:
-  WebXrVrWmrBrowserTest() {
-    // WMR already enabled by default.
-    runtime_requirements_.push_back(XrTestRequirement::DIRECTX_11_1);
-  }
+  WebXrVrWmrBrowserTest();
 };
 
+#if BUILDFLAG(ENABLE_OPENXR)
+class WebXrVrOpenXrBrowserTest : public WebXrVrOpenXrBrowserTestBase {
+ public:
+  WebXrVrOpenXrBrowserTest();
+};
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
 // Test classes with WebXR disabled.
 class WebXrVrOpenVrBrowserTestWebXrDisabled
     : public WebXrVrOpenVrBrowserTestBase {
  public:
-  WebXrVrOpenVrBrowserTestWebXrDisabled() {
-    disable_features_.push_back(features::kWebXr);
-  }
+  WebXrVrOpenVrBrowserTestWebXrDisabled();
 };
 
 class WebXrVrWmrBrowserTestWebXrDisabled : public WebXrVrWmrBrowserTestBase {
  public:
-  WebXrVrWmrBrowserTestWebXrDisabled() {
-    disable_features_.push_back(features::kWebXr);
-  }
+  WebXrVrWmrBrowserTestWebXrDisabled();
 };
+
+#if BUILDFLAG(ENABLE_OPENXR)
+class WebXrVrOpenXrBrowserTestWebXrDisabled
+    : public WebXrVrOpenXrBrowserTestBase {
+ public:
+  WebXrVrOpenXrBrowserTestWebXrDisabled();
+};
+#endif  // BUIDFLAG(ENABLE_OPENXR)
+
 #endif  // OS_WIN
 
 }  // namespace vr
diff --git a/chrome/browser/vr/test/xr_browser_test.cc b/chrome/browser/vr/test/xr_browser_test.cc
index 2cac9c3..648c624 100644
--- a/chrome/browser/vr/test/xr_browser_test.cc
+++ b/chrome/browser/vr/test/xr_browser_test.cc
@@ -41,6 +41,8 @@
 constexpr char XrBrowserTestBase::kVrConfigPathVal[];
 constexpr char XrBrowserTestBase::kVrLogPathEnvVar[];
 constexpr char XrBrowserTestBase::kVrLogPathVal[];
+constexpr char XrBrowserTestBase::kOpenXrConfigPathEnvVar[];
+constexpr char XrBrowserTestBase::kOpenXrConfigPathVal[];
 constexpr char XrBrowserTestBase::kTestFileDir[];
 constexpr char XrBrowserTestBase::kSwitchIgnoreRuntimeRequirements[];
 const std::vector<std::string> XrBrowserTestBase::kRequiredTestSwitches{
@@ -135,6 +137,16 @@
       env_->SetVar(kVrLogPathEnvVar, MakeExecutableRelative(kVrLogPathVal)))
       << "Failed to set OpenVR log location environment variable";
 
+  // Set the environment variable to use the mock OpenXR client.
+  // If the kOpenXrConfigPathEnvVar environment variable is set, the OpenXR
+  // loader will look for the OpenXR runtime specified in that json file. The
+  // json file contains the path to the runtime, relative to the json file
+  // itself. Otherwise, the OpenXR loader loads the active OpenXR runtime
+  // installed on the system, which is specified by a registry key.
+  ASSERT_TRUE(env_->SetVar(kOpenXrConfigPathEnvVar,
+                           MakeExecutableRelative(kOpenXrConfigPathVal)))
+      << "Failed to set OpenXR JSON location environment variable";
+
   // Set any command line flags that subclasses have set, e.g. enabling WebVR
   // and OpenVR support.
   for (const auto& switch_string : append_switches_) {
@@ -164,6 +176,7 @@
     case XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR:
       return device::XrAxisType::kTrackpad;
     case XrBrowserTestBase::RuntimeType::RUNTIME_WMR:
+    case XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR:
       return device::XrAxisType::kJoystick;
     case XrBrowserTestBase::RuntimeType::RUNTIME_NONE:
       return device::XrAxisType::kNone;
@@ -177,6 +190,7 @@
     case XrBrowserTestBase::RuntimeType::RUNTIME_OPENVR:
       return device::XrAxisType::kJoystick;
     case XrBrowserTestBase::RuntimeType::RUNTIME_WMR:
+    case XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR:
       return device::XrAxisType::kTrackpad;
     case XrBrowserTestBase::RuntimeType::RUNTIME_NONE:
       return device::XrAxisType::kNone;
diff --git a/chrome/browser/vr/test/xr_browser_test.h b/chrome/browser/vr/test/xr_browser_test.h
index ad29ac4c..f321f5c 100644
--- a/chrome/browser/vr/test/xr_browser_test.h
+++ b/chrome/browser/vr/test/xr_browser_test.h
@@ -55,6 +55,9 @@
   static constexpr char kVrConfigPathVal[] = "./";
   static constexpr char kVrLogPathEnvVar[] = "VR_LOG_PATH";
   static constexpr char kVrLogPathVal[] = "./";
+  static constexpr char kOpenXrConfigPathEnvVar[] = "XR_RUNTIME_JSON";
+  static constexpr char kOpenXrConfigPathVal[] =
+      "./mock_vr_clients/bin/openxr/openxr.json";
   static constexpr char kTestFileDir[] =
       "chrome/test/data/xr/e2e_test_files/html/";
   static constexpr char kSwitchIgnoreRuntimeRequirements[] =
@@ -71,7 +74,8 @@
   enum class RuntimeType {
     RUNTIME_NONE = 0,
     RUNTIME_OPENVR = 1,
-    RUNTIME_WMR = 2
+    RUNTIME_WMR = 2,
+    RUNTIME_OPENXR = 3
   };
 
   XrBrowserTestBase();
diff --git a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
index dcf6660..75ecdb5 100644
--- a/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_frame_pose_browser_test.cc
@@ -159,6 +159,7 @@
 
 }  // namespace
 
+// TODO(crbug.com/986621) - OpenXR currently hard codes data
 // Pixel test for WebXR - start presentation, submit frames, get data back out.
 // Validates that submitted frames used expected pose.
 WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationPoses) {
diff --git a/chrome/browser/vr/webxr_vr_indicators_browser_test.cc b/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
index 7ba1d1e..435f707 100644
--- a/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_indicators_browser_test.cc
@@ -160,7 +160,11 @@
            UserFriendlyElementName::kWebXrLocationPermissionIndicator, false}});
 }
 
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(
+// TODO(crbug.com/986621) - Enable for OpenXR
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(
+    WebXrVrOpenVrBrowserTest,
+    WebXrVrWmrBrowserTest,
+    WebXrVrBrowserTestBase,
     TestLocationIndicatorWhenUserAskedToPrompt) {
   TestForInitialIndicatorForContentType(
       t, {{CONTENT_SETTINGS_TYPE_GEOLOCATION, CONTENT_SETTING_ASK,
@@ -182,7 +186,12 @@
       });
 }
 
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(
+// TODO(crbug.com/986621) - Enable for OpenXR
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(
+    WebXrVrOpenVrBrowserTest,
+    WebXrVrWmrBrowserTest,
+    WebXrVrBrowserTestBase,
+
     TestMultipleInitialIndicators_OneDeviceAllowed) {
   TestForInitialIndicatorForContentType(
       t,
diff --git a/chrome/browser/vr/webxr_vr_input_browser_test.cc b/chrome/browser/vr/webxr_vr_input_browser_test.cc
index 38d30db1..b06e14a3 100644
--- a/chrome/browser/vr/webxr_vr_input_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_input_browser_test.cc
@@ -214,9 +214,13 @@
   std::move(callback).Run();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Ensure that when an input source's handedness changes, an input source change
 // event is fired and a new input source is created.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputHandednessChange) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestInputHandednessChange) {
   WebXrControllerInputMock my_mock;
   unsigned int controller_index =
       my_mock.CreateAndConnectMinimalGamepad(t->GetPrimaryAxisType());
@@ -254,12 +258,16 @@
   t->EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Test that inputsourceschange events contain only the expected added/removed
 // input sources when a mock controller is connected/disconnected.
 // Also validates that if an input source changes substantially we get an event
 // containing both the removal of the old one and the additon of the new one,
 // rather than two events.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputSourcesChange) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestInputSourcesChange) {
   WebXrControllerInputMock my_mock;
 
   // TODO(crbug.com/963676): Figure out if the race is a product or test bug.
@@ -424,10 +432,14 @@
   EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Ensure that if a Gamepad has the minimum required number of axes/buttons to
 // be considered an xr-standard Gamepad, that it is exposed as such, and that
 // we can check the state of it's priamry axes/button.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadMinimumData) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestGamepadMinimumData) {
   WebXrControllerInputMock my_mock;
 
   unsigned int controller_index =
@@ -467,10 +479,14 @@
   t->EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Ensure that if a Gamepad has all of the required and optional buttons as
 // specified by the xr-standard mapping, that those buttons are plumbed up
 // in their required places.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadCompleteData) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestGamepadCompleteData) {
   WebXrControllerInputMock my_mock;
 
   // Create a controller that supports all reserved buttons.
@@ -682,10 +698,14 @@
   EndTest();
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Test that controller input is registered via WebXR's input method.
 // Equivalent to
 // WebXrVrInputTest#testControllerClicksRegisteredOnDaydream_WebXr.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerInputRegistered) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestControllerInputRegistered) {
   WebXrControllerInputMock my_mock;
 
   unsigned int controller_index =
@@ -768,9 +788,13 @@
   return array_string;
 }
 
+// TODO(crbug.com/986637) - Enable for OpenXR
 // Test that changes in controller position are properly plumbed through to
 // WebXR.
-WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerPositionTracking) {
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTest,
+                                    WebXrVrWmrBrowserTest,
+                                    WebXrVrBrowserTestBase,
+                                    TestControllerPositionTracking) {
   WebXrControllerInputMock my_mock;
 
   auto controller_data = my_mock.CreateValidController(
diff --git a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
index 3ee0462d..4ab26731 100644
--- a/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_pixel_browser_test.cc
@@ -91,6 +91,8 @@
 IN_PROC_BROWSER_TEST_F(WebVrOpenVrBrowserTest, TestPresentationPixels) {
   TestPresentationPixelsImpl(this, "test_webvr_pixels");
 }
+
+// TODO(crbug.com/986621) - OpenXR currently hard codes data
 WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationPixels) {
   TestPresentationPixelsImpl(t, "test_webxr_pixels");
 }
diff --git a/chrome/browser/vr/webxr_vr_transition_browser_test.cc b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
index 3c593c0..4dbdac09 100644
--- a/chrome/browser/vr/webxr_vr_transition_browser_test.cc
+++ b/chrome/browser/vr/webxr_vr_transition_browser_test.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/vr/test/multi_class_browser_test.h"
 #include "chrome/browser/vr/test/webxr_vr_browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "device/vr/buildflags/buildflags.h"
 
 // Browser test equivalent of
 // chrome/android/javatests/src/.../browser/vr/WebXrVrTransitionTest.java.
@@ -53,10 +54,19 @@
   TestApiDisabledWithoutFlagSetImpl(this,
                                     "test_webvr_disabled_without_flag_set");
 }
+
+#if BUILDFLAG(ENABLE_OPENXR)
+IN_PROC_MULTI_CLASS_BROWSER_TEST_F3(WebXrVrOpenVrBrowserTestWebXrDisabled,
+                                    WebXrVrWmrBrowserTestWebXrDisabled,
+                                    WebXrVrOpenXrBrowserTestWebXrDisabled,
+                                    WebXrVrBrowserTestBase,
+                                    TestWebXrDisabledWithoutFlagSet) {
+#else
 IN_PROC_MULTI_CLASS_BROWSER_TEST_F2(WebXrVrOpenVrBrowserTestWebXrDisabled,
                                     WebXrVrWmrBrowserTestWebXrDisabled,
                                     WebXrVrBrowserTestBase,
                                     TestWebXrDisabledWithoutFlagSet) {
+#endif  // BUILDFLAG(ENABLE_OPENXR)
   TestApiDisabledWithoutFlagSetImpl(t, "test_webxr_disabled_without_flag_set");
 }
 
diff --git a/chrome/services/isolated_xr_device/BUILD.gn b/chrome/services/isolated_xr_device/BUILD.gn
index 0474d93..33a5b14 100644
--- a/chrome/services/isolated_xr_device/BUILD.gn
+++ b/chrome/services/isolated_xr_device/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//device/vr/buildflags/buildflags.gni")
+
 source_set("lib") {
   sources = [
     "xr_device_service.cc",
@@ -14,6 +16,10 @@
     "xr_test_hook_wrapper.h",
   ]
 
+  if (enable_openxr) {
+    configs += [ "//third_party/openxr:config" ]
+  }
+
   deps = [
     "//base",
     "//chrome/common",
diff --git a/chrome/services/isolated_xr_device/xr_service_test_hook.cc b/chrome/services/isolated_xr_device/xr_service_test_hook.cc
index fe5d4f1..a8fcfbd 100644
--- a/chrome/services/isolated_xr_device/xr_service_test_hook.cc
+++ b/chrome/services/isolated_xr_device/xr_service_test_hook.cc
@@ -17,18 +17,27 @@
 #include "device/vr/windows_mixed_reality/mixed_reality_statics.h"
 #endif  // BUILDFLAG(ENABLE_WINDOWS_MR)
 
+#if BUILDFLAG(ENABLE_OPENXR)
+#include "device/vr/openxr/openxr_api_wrapper.h"
+#endif  // BUIDLFLAG(ENABLE_OPENXR)
+
 namespace {
 
 void UnsetTestHook(std::unique_ptr<device::XRTestHookWrapper> wrapper) {
+  // Unset the testhook wrapper with the VR runtimes,
+  // so any future calls to them don't use it.
+
 #if BUILDFLAG(ENABLE_OPENVR)
-  // Unset the testhook wrapper with OpenVR, so any
-  // future calls to OpenVR don't use it.
   device::OpenVRWrapper::SetTestHook(nullptr);
 #endif  // BUILDFLAG(ENABLE_OPENVR)
 
 #if BUILDFLAG(ENABLE_WINDOWS_MR)
   device::MixedRealityDeviceStatics::SetTestHook(nullptr);
 #endif  // BUILDFLAG(ENABLE_WINDOWS_MR)
+
+#if BUILDFLAG(ENABLE_OPENXR)
+  device::OpenXrApiWrapper::SetTestHook(nullptr);
+#endif  // BUILDFLAG(ENABLE_OPENXR)
 }
 
 }  // namespace
@@ -43,7 +52,7 @@
       hook ? std::make_unique<XRTestHookWrapper>(hook.PassInterface())
            : nullptr;
 
-  // Register the wrapper testhook with OpenVR and WMR.
+  // Register the wrapper testhook with the VR runtimes
 #if BUILDFLAG(ENABLE_OPENVR)
   OpenVRWrapper::SetTestHook(wrapper.get());
 #endif  // BUILDFLAG(ENABLE_OPENVR)
@@ -52,6 +61,10 @@
   MixedRealityDeviceStatics::SetTestHook(wrapper.get());
 #endif  // BUILDFLAG(ENABLE_WINDOWS_MR)
 
+#if BUILDFLAG(ENABLE_OPENXR)
+  OpenXrApiWrapper::SetTestHook(wrapper.get());
+#endif  // BUILDFLAG(ENABLE_OPENXR)
+
   // Store the new wrapper, so we keep it alive.
   wrapper_ = std::move(wrapper);
 
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index 946f6bf..34c3dff 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -332,6 +332,52 @@
   }
 }
 
+if (enable_openxr) {
+  # The OpenXR Loader by default looks for the path to the OpenXR Runtime from a
+  # registry key, which typically points to the OpenXR runtime installed on the
+  # system. In test, we want to use the mock OpenXR runtime that is created
+  # below in :openxr_mock. If the XR_RUNTIME_JSON environment variable is set,
+  # the OpenXR loader instead looks for the path to the OpenXR runtime in the
+  # json file instead of the registry key. This json file copied to the output
+  # folder points to our mock OpenXR runtime.
+  copy("json_mock") {
+    sources = [
+      "openxr/test/openxr.json",
+    ]
+    outputs = [
+      "$root_out_dir/mock_vr_clients/bin/openxr/openxr.json",
+    ]
+  }
+
+  shared_library("openxr_mock") {
+    testonly = true
+    output_name = "mock_vr_clients/bin/openxr/openxrruntime"
+
+    sources = [
+      "openxr/openxr_util.cc",
+      "openxr/openxr_util.h",
+      "openxr/test/fake_openxr_impl_api.cc",
+      "openxr/test/openxr_negotiate.h",
+      "openxr/test/openxr_test_helper.cc",
+      "openxr/test/openxr_test_helper.h",
+      "test/test_hook.h",
+    ]
+
+    configs += [ "//third_party/openxr:config" ]
+
+    libs = [
+      "d3d11.lib",
+      "DXGI.lib",
+    ]
+
+    deps = [
+      "//base",
+      "//device/vr:json_mock",
+      "//device/vr/public/mojom:test_mojom",
+    ]
+  }
+}
+
 if (enable_gvr_services) {
   java_sources_needing_jni =
       [ "android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java" ]
diff --git a/device/vr/openxr/openxr_api_wrapper.cc b/device/vr/openxr/openxr_api_wrapper.cc
index 1680c0b..a8ec334 100644
--- a/device/vr/openxr/openxr_api_wrapper.cc
+++ b/device/vr/openxr/openxr_api_wrapper.cc
@@ -12,6 +12,7 @@
 #include "base/logging.h"
 #include "device/vr/openxr/openxr_gamepad_helper.h"
 #include "device/vr/openxr/openxr_util.h"
+#include "device/vr/test/test_hook.h"
 #include "ui/gfx/geometry/point3_f.h"
 #include "ui/gfx/geometry/quaternion.h"
 
@@ -130,6 +131,17 @@
   }
 
   DCHECK(IsInitialized());
+
+  if (test_hook_) {
+    // Allow our mock implementation of OpenXR to be controlled by tests.
+    // The mock implementation of xrCreateInstance returns a pointer to the
+    // service test hook (g_test_helper) as the instance.
+    service_test_hook_ = reinterpret_cast<ServiceTestHook*>(instance_);
+    service_test_hook_->SetTestHook(test_hook_);
+
+    test_hook_->AttachCurrentThread();
+  }
+
   return true;
 }
 
@@ -145,6 +157,9 @@
     xrDestroyInstance(instance_);
   }
 
+  if (test_hook_)
+    test_hook_->DetachCurrentThread();
+
   Reset();
 }
 
@@ -403,7 +418,7 @@
   wait_info.timeout = XR_INFINITE_DURATION;
 
   RETURN_IF_XR_FAILED(xrWaitSwapchainImage(color_swapchain_, &wait_info));
-  RETURN_IF_XR_FAILED(UpdateProjectionLayers());
+  RETURN_IF_XR_FAILED(UpdateProjectionLayers(frame_state.predictedDisplayTime));
 
   *texture = color_swapchain_images_[color_swapchain_image_index].texture;
   frame_state_ = frame_state;
@@ -445,13 +460,14 @@
   return xr_result;
 }
 
-XrResult OpenXrApiWrapper::UpdateProjectionLayers() {
+XrResult OpenXrApiWrapper::UpdateProjectionLayers(
+    XrTime predicted_display_time) {
   XrResult xr_result;
 
   XrViewState view_state = {XR_TYPE_VIEW_STATE};
 
   XrViewLocateInfo view_locate_info = {XR_TYPE_VIEW_LOCATE_INFO};
-  view_locate_info.displayTime = frame_state_.predictedDisplayTime;
+  view_locate_info.displayTime = predicted_display_time;
   view_locate_info.space = local_space_;
 
   uint32_t view_count = 0;
@@ -506,6 +522,9 @@
   RETURN_IF_XR_FAILED(xrLocateSpace(
       view_space_, local_space_, frame_state_.predictedDisplayTime, &relation));
 
+  DCHECK(relation.relationFlags & XR_SPACE_RELATION_ORIENTATION_VALID_BIT);
+  DCHECK(relation.relationFlags & XR_SPACE_RELATION_POSITION_VALID_BIT);
+
   orientation->set_x(relation.pose.orientation.x);
   orientation->set_y(relation.pose.orientation.y);
   orientation->set_z(relation.pose.orientation.z);
@@ -559,4 +578,16 @@
       ->recommendedSwapchainSampleCount;
 }
 
+VRTestHook* OpenXrApiWrapper::test_hook_ = nullptr;
+ServiceTestHook* OpenXrApiWrapper::service_test_hook_ = nullptr;
+void OpenXrApiWrapper::SetTestHook(VRTestHook* hook) {
+  // This may be called from any thread - tests are responsible for
+  // maintaining thread safety, typically by not changing the test hook
+  // while presenting.
+  test_hook_ = hook;
+  if (service_test_hook_) {
+    service_test_hook_->SetTestHook(test_hook_);
+  }
+}
+
 }  // namespace device
diff --git a/device/vr/openxr/openxr_api_wrapper.h b/device/vr/openxr/openxr_api_wrapper.h
index dcb97426..1fc638dc 100644
--- a/device/vr/openxr/openxr_api_wrapper.h
+++ b/device/vr/openxr/openxr_api_wrapper.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "device/vr/vr_export.h"
 #include "third_party/openxr/include/openxr/openxr.h"
 #include "third_party/openxr/include/openxr/openxr_platform.h"
 
@@ -23,6 +24,8 @@
 namespace device {
 
 class OpenXrGamepadHelper;
+class VRTestHook;
+class ServiceTestHook;
 
 class OpenXrApiWrapper {
  public:
@@ -34,6 +37,8 @@
   static bool IsHardwareAvailable();
   static bool IsApiAvailable();
 
+  static VRTestHook* GetTestHook();
+
   XrResult StartSession(const Microsoft::WRL::ComPtr<ID3D11Device>& d3d_device,
                         std::unique_ptr<OpenXrGamepadHelper>* gamepad_helper);
 
@@ -45,6 +50,8 @@
 
   XrTime GetPredictedDisplayTime() const;
 
+  static void DEVICE_VR_EXPORT SetTestHook(VRTestHook* hook);
+
   void GetViewSize(uint32_t* width, uint32_t* height) const;
   XrResult GetLuid(LUID* luid) const;
 
@@ -66,7 +73,7 @@
       std::unique_ptr<OpenXrGamepadHelper>* gamepad_helper);
 
   XrResult BeginSession();
-  XrResult UpdateProjectionLayers();
+  XrResult UpdateProjectionLayers(XrTime predicted_display_time);
 
   bool HasInstance() const;
   bool HasSystem() const;
@@ -78,7 +85,11 @@
 
   uint32_t GetRecommendedSwapchainSampleCount() const;
 
-  // OpenXr objects
+  // Testing objects
+  static VRTestHook* test_hook_;
+  static ServiceTestHook* service_test_hook_;
+
+  // OpenXR objects
 
   // These objects are valid on successful initialization.
   XrInstance instance_;
diff --git a/device/vr/openxr/openxr_render_loop.cc b/device/vr/openxr/openxr_render_loop.cc
index 1c656b1..8da8e69 100644
--- a/device/vr/openxr/openxr_render_loop.cc
+++ b/device/vr/openxr/openxr_render_loop.cc
@@ -20,17 +20,16 @@
 }
 
 mojom::XRFrameDataPtr OpenXrRenderLoop::GetNextFrameData() {
-  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
+  frame_data->frame_id = next_frame_id_;
 
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
   if (XR_FAILED(openxr_->BeginFrame(&texture))) {
-    return nullptr;
+    return frame_data;
   }
 
   texture_helper_.SetBackbuffer(texture.Get());
 
-  mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
-  frame_data->frame_id = next_frame_id_;
-
   frame_data->time_delta =
       base::TimeDelta::FromNanoseconds(openxr_->GetPredictedDisplayTime());
 
diff --git a/device/vr/openxr/openxr_util.h b/device/vr/openxr/openxr_util.h
index 663fb7d..91969e0 100644
--- a/device/vr/openxr/openxr_util.h
+++ b/device/vr/openxr/openxr_util.h
@@ -30,6 +30,14 @@
       return XR_STATE_UNAVAILABLE;                \
   } while (false)
 
+#define RETURN_IF_FALSE(condition, error_code, msg) \
+  do {                                              \
+    if (!(condition)) {                             \
+      LOG(ERROR) << __FUNCTION__ << ": " << msg;    \
+      return error_code;                            \
+    }                                               \
+  } while (false)
+
 // Returns the identity pose, where the position is {0, 0, 0} and the
 // orientation is {0, 0, 0, 1}.
 XrPosef PoseIdentity();
diff --git a/device/vr/openxr/test/DEPS b/device/vr/openxr/test/DEPS
new file mode 100644
index 0000000..11b30de
--- /dev/null
+++ b/device/vr/openxr/test/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+third_party/openxr/include/openxr",
+]
diff --git a/device/vr/openxr/test/fake_openxr_impl_api.cc b/device/vr/openxr/test/fake_openxr_impl_api.cc
new file mode 100644
index 0000000..4a63bd0
--- /dev/null
+++ b/device/vr/openxr/test/fake_openxr_impl_api.cc
@@ -0,0 +1,488 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <directxmath.h>
+#include <wrl.h>
+
+#include "device/vr/openxr/openxr_util.h"
+#include "device/vr/openxr/test/openxr_negotiate.h"
+#include "device/vr/openxr/test/openxr_test_helper.h"
+
+namespace {
+// Global test helper that communicates with the test and contains the mock
+// OpenXR runtime state/properties. A reference to this is returned as the
+// instance handle through xrCreateInstance.
+OpenXrTestHelper g_test_helper;
+}  // namespace
+
+// Mock implementations of openxr runtime.dll APIs.
+// Please add new APIs in alphabetical order.
+
+XrResult xrAcquireSwapchainImage(
+    XrSwapchain swapchain,
+    const XrSwapchainImageAcquireInfo* acquire_info,
+    uint32_t* index) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(acquire_info->type == XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrAcquireSwapchainImage type invalid");
+
+  *index = g_test_helper.NextSwapchainImageIndex();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrBeginFrame(XrSession session,
+                      const XrFrameBeginInfo* frame_begin_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(frame_begin_info->type == XR_TYPE_FRAME_BEGIN_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "XrFrameBeginInfo type invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult xrBeginSession(XrSession session,
+                        const XrSessionBeginInfo* begin_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(begin_info->type == XR_TYPE_SESSION_BEGIN_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSessionBeginInfo type invalid");
+  RETURN_IF_FALSE(begin_info->primaryViewConfigurationType ==
+                      XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSessionBeginInfo primaryViewConfigurationType invalid");
+
+  RETURN_IF_XR_FAILED(g_test_helper.BeginSession());
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateInstance(const XrInstanceCreateInfo* create_info,
+                          XrInstance* instance) {
+  DLOG(INFO) << __FUNCTION__;
+
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_INSTANCE_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrInstanceCreateInfo type invalid");
+
+  RETURN_IF_FALSE(create_info->enabledExtensionCount ==
+                      OpenXrTestHelper::NumExtensionsSupported(),
+                  XR_ERROR_VALIDATION_FAILURE, "enabledExtensionCount invalid");
+
+  for (uint32_t i = 0; i < create_info->enabledExtensionCount; i++) {
+    bool valid_extension = false;
+    for (size_t j = 0; j < OpenXrTestHelper::NumExtensionsSupported(); j++) {
+      if (strcmp(create_info->enabledExtensionNames[i],
+                 OpenXrTestHelper::kExtensions[j]) == 0) {
+        valid_extension = true;
+        break;
+      }
+    }
+
+    RETURN_IF_FALSE(valid_extension, XR_ERROR_VALIDATION_FAILURE,
+                    "enabledExtensionNames contains invalid extensions");
+  }
+
+  // Return the test helper object back to the OpenXrAPIWrapper so it can use
+  // it as the TestHookRegistration.
+  *instance = reinterpret_cast<XrInstance>(&g_test_helper);
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateReferenceSpace(XrSession session,
+                                const XrReferenceSpaceCreateInfo* create_info,
+                                XrSpace* space) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_REFERENCE_SPACE_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrReferenceSpaceCreateInfo type invalid");
+  RETURN_IF_FALSE(
+      create_info->referenceSpaceType == XR_REFERENCE_SPACE_TYPE_LOCAL ||
+          create_info->referenceSpaceType == XR_REFERENCE_SPACE_TYPE_VIEW,
+      XR_ERROR_VALIDATION_FAILURE,
+      "XrReferenceSpaceCreateInfo referenceSpaceType invalid");
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateXrPosefIsIdentity(
+      create_info->poseInReferenceSpace));
+
+  switch (create_info->referenceSpaceType) {
+    case XR_REFERENCE_SPACE_TYPE_LOCAL:
+      *space = g_test_helper.CreateLocalSpace();
+      break;
+    case XR_REFERENCE_SPACE_TYPE_VIEW:
+      *space = g_test_helper.CreateViewSpace();
+      break;
+    default:
+      RETURN_IF_FALSE(false, XR_ERROR_VALIDATION_FAILURE,
+                      "XrReferenceSpaceCreateInfo referenceSpaceType invalid");
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateSession(XrInstance instance,
+                         const XrSessionCreateInfo* create_info,
+                         XrSession* session) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_SESSION_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSessionCreateInfo type invalid");
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(create_info->systemId));
+
+  const XrGraphicsBindingD3D11KHR* binding =
+      static_cast<const XrGraphicsBindingD3D11KHR*>(create_info->next);
+  RETURN_IF_FALSE(binding->type == XR_TYPE_GRAPHICS_BINDING_D3D11_KHR,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrGraphicsBindingD3D11KHR type invalid");
+  RETURN_IF_FALSE(binding->device != nullptr, XR_ERROR_VALIDATION_FAILURE,
+                  "D3D11Device is null");
+
+  g_test_helper.SetD3DDevice(binding->device);
+  *session = g_test_helper.GetSession();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrCreateSwapchain(XrSession session,
+                           const XrSwapchainCreateInfo* create_info,
+                           XrSwapchain* swapchain) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(create_info->type == XR_TYPE_SWAPCHAIN_CREATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo type invalid");
+  RETURN_IF_FALSE(create_info->arraySize == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo arraySize invalid");
+  RETURN_IF_FALSE(create_info->format == DXGI_FORMAT_R8G8B8A8_UNORM,
+                  XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED,
+                  "XrSwapchainCreateInfo format unsupported");
+  RETURN_IF_FALSE(create_info->width == OpenXrTestHelper::kDimension * 2,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo width is not dimension * 2");
+  RETURN_IF_FALSE(create_info->height == OpenXrTestHelper::kDimension,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo height is not dimension");
+  RETURN_IF_FALSE(create_info->mipCount == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo mipCount is not 1");
+  RETURN_IF_FALSE(create_info->faceCount == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo faceCount is not 1");
+  RETURN_IF_FALSE(create_info->sampleCount == OpenXrTestHelper::kSwapCount,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchainCreateInfo sampleCount invalid");
+  RETURN_IF_FALSE(
+      create_info->usageFlags == XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
+      XR_ERROR_VALIDATION_FAILURE,
+      "XrSwapchainCreateInfo usageFlags is not "
+      "XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT");
+
+  *swapchain = g_test_helper.GetSwapchain();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrDestroyInstance(XrInstance instance) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEndFrame(XrSession session, const XrFrameEndInfo* frame_end_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(frame_end_info->type == XR_TYPE_FRAME_END_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "frame_end_info type invalid");
+  RETURN_IF_FALSE(frame_end_info->environmentBlendMode ==
+                      OpenXrTestHelper::kEnvironmentBlendMode,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "frame_end_info environmentBlendMode invalid");
+  RETURN_IF_FALSE(frame_end_info->layerCount == 1, XR_ERROR_VALIDATION_FAILURE,
+                  "frame_end_info layerCount invalid");
+  RETURN_IF_XR_FAILED(
+      g_test_helper.ValidatePredictedDisplayTime(frame_end_info->displayTime));
+
+  g_test_helper.OnPresentedFrame();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEndSession(XrSession session) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_XR_FAILED(g_test_helper.EndSession());
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateEnvironmentBlendModes(
+    XrInstance instance,
+    XrSystemId system_id,
+    uint32_t environmentBlendModeCapacityInput,
+    uint32_t* environmentBlendModeCountOutput,
+    XrEnvironmentBlendMode* environmentBlendModes) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+
+  *environmentBlendModeCountOutput = 1;
+
+  if (environmentBlendModeCapacityInput != 0) {
+    *environmentBlendModes = OpenXrTestHelper::kEnvironmentBlendMode;
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateInstanceExtensionProperties(
+    const char* layer_name,
+    uint32_t property_capacity_input,
+    uint32_t* property_count_output,
+    XrExtensionProperties* properties) {
+  DLOG(INFO) << __FUNCTION__;
+
+  RETURN_IF_FALSE(
+      property_capacity_input >= OpenXrTestHelper::NumExtensionsSupported() ||
+          property_capacity_input == 0,
+      XR_ERROR_SIZE_INSUFFICIENT, "XrExtensionProperties array is too small");
+
+  *property_count_output = OpenXrTestHelper::NumExtensionsSupported();
+
+  if (property_capacity_input != 0) {
+    for (uint32_t i = 0; i < OpenXrTestHelper::NumExtensionsSupported(); i++) {
+      errno_t error = strcpy_s(properties[i].extensionName,
+                               OpenXrTestHelper::kExtensions[i]);
+      DCHECK(error == 0);
+      properties[i].specVersion = 1;
+    }
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateViewConfigurationViews(
+    XrInstance instance,
+    XrSystemId system_id,
+    XrViewConfigurationType view_configuration_type,
+    uint32_t view_capacity_input,
+    uint32_t* view_count_output,
+    XrViewConfigurationView* views) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+  RETURN_IF_FALSE(
+      view_configuration_type == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
+      XR_ERROR_VALIDATION_FAILURE,
+      "xrEnumerateViewConfigurationViews viewConfigurationType invalid");
+
+  *view_count_output = OpenXrTestHelper::NumViews();
+
+  if (view_capacity_input != 0) {
+    views[0] = OpenXrTestHelper::kViewConfigurationViews[0];
+    views[1] = OpenXrTestHelper::kViewConfigurationViews[1];
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrEnumerateSwapchainImages(XrSwapchain swapchain,
+                                    uint32_t image_capacity_input,
+                                    uint32_t* image_count_output,
+                                    XrSwapchainImageBaseHeader* images) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(
+      image_capacity_input == OpenXrTestHelper::kMinSwapchainBuffering ||
+          image_capacity_input == 0,
+      XR_ERROR_SIZE_INSUFFICIENT,
+      "xrEnumerateSwapchainImages does not equal length returned by "
+      "xrCreateSwapchain");
+
+  *image_count_output = OpenXrTestHelper::kMinSwapchainBuffering;
+
+  if (image_capacity_input != 0) {
+    const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>& textures =
+        g_test_helper.GetSwapchainTextures();
+    DCHECK(textures.size() == image_capacity_input);
+
+    for (uint32_t i = 0; i < image_capacity_input; i++) {
+      XrSwapchainImageD3D11KHR& image =
+          reinterpret_cast<XrSwapchainImageD3D11KHR*>(images)[i];
+
+      RETURN_IF_FALSE(image.type == XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR,
+                      XR_ERROR_VALIDATION_FAILURE,
+                      "XrSwapchainImageD3D11KHR type invalid");
+
+      image.texture = textures[i].Get();
+    }
+  }
+
+  return XR_SUCCESS;
+}
+
+XrResult xrGetD3D11GraphicsRequirementsKHR(
+    XrInstance instance,
+    XrSystemId system_id,
+    XrGraphicsRequirementsD3D11KHR* graphics_requirements) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSystemId(system_id));
+  RETURN_IF_FALSE(
+      graphics_requirements->type == XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR,
+      XR_ERROR_VALIDATION_FAILURE,
+      "XrGraphicsRequirementsD3D11KHR type invalid");
+
+  Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory;
+  Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
+  HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory));
+  DCHECK(SUCCEEDED(hr));
+  for (int i = 0; SUCCEEDED(dxgi_factory->EnumAdapters(i, &adapter)); i++) {
+    DXGI_ADAPTER_DESC desc;
+    adapter->GetDesc(&desc);
+    graphics_requirements->adapterLuid = desc.AdapterLuid;
+
+    // Require D3D11.1 to support shared NT handles.
+    graphics_requirements->minFeatureLevel = D3D_FEATURE_LEVEL_11_1;
+
+    return XR_SUCCESS;
+  }
+
+  RETURN_IF_FALSE(false, XR_ERROR_VALIDATION_FAILURE,
+                  "Unable to create query DXGI Adapter");
+}
+
+XrResult xrGetSystem(XrInstance instance,
+                     const XrSystemGetInfo* get_info,
+                     XrSystemId* system_id) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateInstance(instance));
+  RETURN_IF_FALSE(get_info->type == XR_TYPE_SYSTEM_GET_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "XrSystemGetInfo type invalid");
+  RETURN_IF_FALSE(get_info->formFactor == XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrSystemGetInfo formFactor invalid");
+
+  *system_id = g_test_helper.GetSystemId();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrLocateSpace(XrSpace space,
+                       XrSpace baseSpace,
+                       XrTime time,
+                       XrSpaceRelation* relation) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(space));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(baseSpace));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidatePredictedDisplayTime(time));
+
+  g_test_helper.GetPose(&(relation->pose));
+
+  relation->relationFlags = XR_SPACE_RELATION_ORIENTATION_VALID_BIT |
+                            XR_SPACE_RELATION_POSITION_VALID_BIT;
+
+  return XR_SUCCESS;
+}
+
+XrResult xrLocateViews(XrSession session,
+                       const XrViewLocateInfo* view_locate_info,
+                       XrViewState* view_state,
+                       uint32_t view_capacity_input,
+                       uint32_t* view_count_output,
+                       XrView* views) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_XR_FAILED(g_test_helper.ValidatePredictedDisplayTime(
+      view_locate_info->displayTime));
+  RETURN_IF_FALSE(view_locate_info->type == XR_TYPE_VIEW_LOCATE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrLocateViews view_locate_info type invalid");
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSpace(view_locate_info->space));
+
+  return XR_SUCCESS;
+}
+
+XrResult xrReleaseSwapchainImage(
+    XrSwapchain swapchain,
+    const XrSwapchainImageReleaseInfo* release_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(release_info->type == XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrReleaseSwapchainImage type invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult xrWaitFrame(XrSession session,
+                     const XrFrameWaitInfo* frame_wait_info,
+                     XrFrameState* frame_state) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSession(session));
+  RETURN_IF_FALSE(frame_wait_info->type == XR_TYPE_FRAME_WAIT_INFO,
+                  XR_ERROR_VALIDATION_FAILURE, "frame_wait_info type invalid");
+  RETURN_IF_FALSE(frame_state->type == XR_TYPE_FRAME_STATE,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XR_TYPE_FRAME_STATE type invalid");
+
+  frame_state->predictedDisplayTime = g_test_helper.NextPredictedDisplayTime();
+
+  return XR_SUCCESS;
+}
+
+XrResult xrWaitSwapchainImage(XrSwapchain swapchain,
+                              const XrSwapchainImageWaitInfo* wait_info) {
+  DLOG(INFO) << __FUNCTION__;
+  XrResult xr_result;
+
+  RETURN_IF_XR_FAILED(g_test_helper.ValidateSwapchain(swapchain));
+  RETURN_IF_FALSE(wait_info->type == XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrWaitSwapchainImage type invalid");
+  RETURN_IF_FALSE(wait_info->timeout == XR_INFINITE_DURATION,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "xrWaitSwapchainImage timeout not XR_INFINITE_DURATION");
+
+  return XR_SUCCESS;
+}
diff --git a/device/vr/openxr/test/openxr.json b/device/vr/openxr/test/openxr.json
new file mode 100644
index 0000000..af26106b
--- /dev/null
+++ b/device/vr/openxr/test/openxr.json
@@ -0,0 +1,6 @@
+{
+  "file_format_version": "1.0.0",
+  "runtime": {
+    "library_path": ".\\OpenXrRuntime.dll"
+  }
+}
diff --git a/device/vr/openxr/test/openxr_negotiate.h b/device/vr/openxr/test/openxr_negotiate.h
new file mode 100644
index 0000000..fd5dfdb9
--- /dev/null
+++ b/device/vr/openxr/test/openxr_negotiate.h
@@ -0,0 +1,93 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
+#define DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
+
+#include <d3d11.h>
+#include <unknwn.h>
+
+#include "third_party/openxr/include/openxr/loader_interfaces.h"
+#include "third_party/openxr/include/openxr/openxr.h"
+#include "third_party/openxr/include/openxr/openxr_platform.h"
+
+// This file contains functions that are used by the openxr_loader.dll to call
+// into the fake OpenXR Runtime. Used for testing purposes only, so this should
+// only be used to call the fake OpenXR APIs defined in
+// fake_openxr_impl_api.cc.
+
+// Please add new OpenXR APIs below in alphabetical order.
+XrResult GetInstanceProcAddress(XrInstance instance,
+                                const char* name,
+                                PFN_xrVoidFunction* function) {
+  if (strcmp(name, "xrAcquireSwapchainImage") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrAcquireSwapchainImage);
+  } else if (strcmp(name, "xrBeginFrame") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrBeginFrame);
+  } else if (strcmp(name, "xrBeginSession") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrBeginSession);
+  } else if (strcmp(name, "xrCreateInstance") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateInstance);
+  } else if (strcmp(name, "xrCreateReferenceSpace") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateReferenceSpace);
+  } else if (strcmp(name, "xrCreateSession") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateSession);
+  } else if (strcmp(name, "xrCreateSwapchain") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrCreateSwapchain);
+  } else if (strcmp(name, "xrDestroyInstance") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrDestroyInstance);
+  } else if (strcmp(name, "xrEndFrame") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrEndFrame);
+  } else if (strcmp(name, "xrEndSession") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrEndSession);
+  } else if (strcmp(name, "xrEnumerateEnvironmentBlendModes") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateEnvironmentBlendModes);
+  } else if (strcmp(name, "xrEnumerateInstanceExtensionProperties") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(
+        xrEnumerateInstanceExtensionProperties);
+  } else if (strcmp(name, "xrEnumerateSwapchainImages") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateSwapchainImages);
+  } else if (strcmp(name, "xrEnumerateViewConfigurationViews") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrEnumerateViewConfigurationViews);
+  } else if (strcmp(name, "xrGetD3D11GraphicsRequirementsKHR") == 0) {
+    *function =
+        reinterpret_cast<PFN_xrVoidFunction>(xrGetD3D11GraphicsRequirementsKHR);
+  } else if (strcmp(name, "xrGetSystem") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrGetSystem);
+  } else if (strcmp(name, "xrLocateSpace") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrLocateSpace);
+  } else if (strcmp(name, "xrLocateViews") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrLocateViews);
+  } else if (strcmp(name, "xrReleaseSwapchainImage") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrReleaseSwapchainImage);
+  } else if (strcmp(name, "xrWaitFrame") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrWaitFrame);
+  } else if (strcmp(name, "xrWaitSwapchainImage") == 0) {
+    *function = reinterpret_cast<PFN_xrVoidFunction>(xrWaitSwapchainImage);
+  } else {
+    return XR_ERROR_FUNCTION_UNSUPPORTED;
+  }
+
+  return XR_SUCCESS;
+}
+
+// The single exported function in fake OpenXR Runtime DLL which the OpenXR
+// loader calls for negotiation. GetInstanceProcAddress is returned to the
+// loader, which is then used by the loader to call OpenXR APIs.
+// extern "C" is needed because the OpenXR Loader expects the name of this
+// function to be unmangled. The real OpenXR runtime does this as well.
+extern "C" __declspec(dllexport) XrResult xrNegotiateLoaderRuntimeInterface(
+    const XrNegotiateLoaderInfo* loaderInfo,
+    XrNegotiateRuntimeRequest* runtimeRequest) {
+  runtimeRequest->runtimeInterfaceVersion = 1;
+  runtimeRequest->runtimeXrVersion = XR_MAKE_VERSION(0, 1, 0);
+  runtimeRequest->getInstanceProcAddr = GetInstanceProcAddress;
+
+  return XR_SUCCESS;
+}
+
+#endif  // DEVICE_VR_OPENXR_TEST_OPENXR_NEGOTIATE_H_
diff --git a/device/vr/openxr/test/openxr_test_helper.cc b/device/vr/openxr/test/openxr_test_helper.cc
new file mode 100644
index 0000000..6a1dad9
--- /dev/null
+++ b/device/vr/openxr/test/openxr_test_helper.cc
@@ -0,0 +1,280 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/openxr/test/openxr_test_helper.h"
+
+#include <cmath>
+#include <limits>
+
+#include "device/vr/openxr/openxr_util.h"
+#include "third_party/openxr/include/openxr/openxr_platform.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+
+// Initialize static variables in OpenXrTestHelper.
+const char* OpenXrTestHelper::kExtensions[] = {
+    XR_KHR_D3D11_ENABLE_EXTENSION_NAME};
+const uint32_t OpenXrTestHelper::kDimension = 128;
+const uint32_t OpenXrTestHelper::kSwapCount = 1;
+const uint32_t OpenXrTestHelper::kMinSwapchainBuffering = 3;
+const uint32_t OpenXrTestHelper::kMaxViewCount = 2;
+const XrViewConfigurationView OpenXrTestHelper::kViewConfigView = {
+    XR_TYPE_VIEW_CONFIGURATION_VIEW, nullptr,
+    OpenXrTestHelper::kDimension,    OpenXrTestHelper::kDimension,
+    OpenXrTestHelper::kDimension,    OpenXrTestHelper::kDimension,
+    OpenXrTestHelper::kSwapCount,    OpenXrTestHelper::kSwapCount};
+XrViewConfigurationView OpenXrTestHelper::kViewConfigurationViews[] = {
+    OpenXrTestHelper::kViewConfigView, OpenXrTestHelper::kViewConfigView};
+const XrEnvironmentBlendMode OpenXrTestHelper::kEnvironmentBlendMode =
+    XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
+
+uint32_t OpenXrTestHelper::NumExtensionsSupported() {
+  return sizeof(kExtensions) / sizeof(kExtensions[0]);
+}
+
+uint32_t OpenXrTestHelper::NumViews() {
+  return sizeof(kViewConfigurationViews) / sizeof(kViewConfigurationViews[0]);
+}
+
+OpenXrTestHelper::OpenXrTestHelper()
+    : system_id_(0),
+      session_(XR_NULL_HANDLE),
+      swapchain_(XR_NULL_HANDLE),
+      local_space_(XR_NULL_HANDLE),
+      view_space_(XR_NULL_HANDLE),
+      session_running_(false),
+      acquired_swapchain_texture_(0),
+      next_action_space_(0),
+      next_predicted_display_time_(0) {}
+
+OpenXrTestHelper::~OpenXrTestHelper() = default;
+
+void OpenXrTestHelper::TestFailure() {
+  NOTREACHED();
+}
+
+void OpenXrTestHelper::SetTestHook(device::VRTestHook* hook) {
+  base::AutoLock auto_lock(lock_);
+  test_hook_ = hook;
+}
+
+void OpenXrTestHelper::OnPresentedFrame() {
+  static uint32_t frame_id = 1;
+
+  base::AutoLock auto_lock(lock_);
+  if (!test_hook_)
+    return;
+
+  // TODO(https://crbug.com/986621): The frame color is currently hard-coded to
+  // what the pixel tests expects. We should instead store the actual WebGL
+  // texture and read from it, which will also verify the correct swapchain
+  // texture was used.
+
+  device::DeviceConfig device_config = test_hook_->WaitGetDeviceConfig();
+  device::SubmittedFrameData frame_data = {};
+
+  if (std::abs(device_config.interpupillary_distance - 0.2f) <
+      std::numeric_limits<float>::epsilon()) {
+    // TestPresentationPoses sets the ipd to 0.2f, whereas tests by default have
+    // an ipd of 0.1f. This test has specific formulas to determine the colors,
+    // specified in test_webxr_poses.html.
+    frame_data.color = {
+        frame_id % 256, ((frame_id - frame_id % 256) / 256) % 256,
+        ((frame_id - frame_id % (256 * 256)) / (256 * 256)) % 256, 255};
+  } else {
+    // The WebXR tests by default clears to blue. TestPresentationPixels
+    // verifies this color.
+    frame_data.color = {0, 0, 255, 255};
+  }
+
+  frame_data.left_eye = true;
+  test_hook_->OnFrameSubmitted(frame_data);
+
+  frame_data.left_eye = false;
+  test_hook_->OnFrameSubmitted(frame_data);
+
+  frame_id++;
+}
+
+XrSystemId OpenXrTestHelper::GetSystemId() {
+  system_id_ = 1;
+  return system_id_;
+}
+
+XrSession OpenXrTestHelper::GetSession() {
+  // reinterpret_cast needed because XrSession is a pointer type.
+  session_ = reinterpret_cast<XrSession>(2);
+  return session_;
+}
+
+XrSwapchain OpenXrTestHelper::GetSwapchain() {
+  // reinterpret_cast needed because XrSwapchain is a pointer type.
+  swapchain_ = reinterpret_cast<XrSwapchain>(3);
+  return swapchain_;
+}
+
+XrSpace OpenXrTestHelper::CreateLocalSpace() {
+  // reinterpret_cast needed because XrSpace is a pointer type.
+  local_space_ = reinterpret_cast<XrSpace>(++next_action_space_);
+  return local_space_;
+}
+
+XrSpace OpenXrTestHelper::CreateViewSpace() {
+  // reinterpret_cast needed because XrSpace is a pointer type.
+  view_space_ = reinterpret_cast<XrSpace>(++next_action_space_);
+  return view_space_;
+}
+
+XrResult OpenXrTestHelper::BeginSession() {
+  RETURN_IF_FALSE(!session_running_, XR_ERROR_SESSION_RUNNING,
+                  "Session is already running");
+
+  session_running_ = true;
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::EndSession() {
+  RETURN_IF_FALSE(session_running_, XR_ERROR_SESSION_NOT_RUNNING,
+                  "Session is not currently running");
+
+  session_running_ = false;
+  return XR_SUCCESS;
+}
+
+void OpenXrTestHelper::SetD3DDevice(ID3D11Device* d3d_device) {
+  DCHECK(d3d_device_ == nullptr);
+  DCHECK(d3d_device != nullptr);
+  d3d_device_ = d3d_device;
+
+  D3D11_TEXTURE2D_DESC desc{};
+  desc.Width = kDimension * 2;  // Using a double wide texture
+  desc.Height = kDimension;
+  desc.MipLevels = 1;
+  desc.ArraySize = 1;
+  desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+  desc.SampleDesc.Count = 1;
+  desc.Usage = D3D11_USAGE_DEFAULT;
+  desc.BindFlags = D3D11_BIND_RENDER_TARGET;
+
+  for (uint32_t i = 0; i < kMinSwapchainBuffering; i++) {
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
+    HRESULT hr = d3d_device_->CreateTexture2D(&desc, nullptr, &texture);
+    DCHECK(hr == S_OK);
+
+    textures_arr_.push_back(texture);
+  }
+}
+
+const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
+OpenXrTestHelper::GetSwapchainTextures() const {
+  return textures_arr_;
+}
+
+uint32_t OpenXrTestHelper::NextSwapchainImageIndex() {
+  acquired_swapchain_texture_ =
+      (acquired_swapchain_texture_ + 1) % textures_arr_.size();
+  return acquired_swapchain_texture_;
+}
+
+XrTime OpenXrTestHelper::NextPredictedDisplayTime() {
+  return ++next_predicted_display_time_;
+}
+
+void OpenXrTestHelper::GetPose(XrPosef* pose) {
+  *pose = device::PoseIdentity();
+
+  base::AutoLock lock(lock_);
+  if (test_hook_) {
+    device::PoseFrameData pose_data = test_hook_->WaitGetPresentingPose();
+    if (pose_data.is_valid) {
+      gfx::Transform transform = PoseFrameDataToTransform(pose_data);
+
+      gfx::DecomposedTransform decomposed_transform;
+      bool decomposable =
+          gfx::DecomposeTransform(&decomposed_transform, transform);
+      DCHECK(decomposable);
+
+      pose->orientation.x = decomposed_transform.quaternion.x();
+      pose->orientation.y = decomposed_transform.quaternion.y();
+      pose->orientation.z = decomposed_transform.quaternion.z();
+      pose->orientation.w = decomposed_transform.quaternion.w();
+
+      pose->position.x = decomposed_transform.translate[0];
+      pose->position.y = decomposed_transform.translate[1];
+      pose->position.z = decomposed_transform.translate[2];
+    }
+  }
+}
+
+XrResult OpenXrTestHelper::ValidateInstance(XrInstance instance) const {
+  // The Fake OpenXR Runtime returns this global OpenXrTestHelper object as the
+  // instance value on xrCreateInstance.
+  RETURN_IF_FALSE(reinterpret_cast<OpenXrTestHelper*>(instance) == this,
+                  XR_ERROR_VALIDATION_FAILURE, "XrInstance invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSystemId(XrSystemId system_id) const {
+  RETURN_IF_FALSE(system_id_ != 0, XR_ERROR_SYSTEM_INVALID,
+                  "XrSystemId has not been queried");
+  RETURN_IF_FALSE(system_id == system_id_, XR_ERROR_SYSTEM_INVALID,
+                  "XrSystemId invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSession(XrSession session) const {
+  RETURN_IF_FALSE(session_ != XR_NULL_HANDLE, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSession has not been queried");
+  RETURN_IF_FALSE(session == session_, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSession invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSwapchain(XrSwapchain swapchain) const {
+  RETURN_IF_FALSE(swapchain_ != XR_NULL_HANDLE, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchain has not been queried");
+  RETURN_IF_FALSE(swapchain == swapchain_, XR_ERROR_VALIDATION_FAILURE,
+                  "XrSwapchain invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateSpace(XrSpace space) const {
+  RETURN_IF_FALSE(space != XR_NULL_HANDLE, XR_ERROR_HANDLE_INVALID,
+                  "XrSpace has not been queried");
+  RETURN_IF_FALSE(reinterpret_cast<uint32_t>(space) <= next_action_space_,
+                  XR_ERROR_HANDLE_INVALID, "XrSpace invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidatePredictedDisplayTime(XrTime time) const {
+  RETURN_IF_FALSE(time != 0, XR_ERROR_VALIDATION_FAILURE,
+                  "XrTime has not been queried");
+  RETURN_IF_FALSE(time <= next_predicted_display_time_,
+                  XR_ERROR_VALIDATION_FAILURE,
+                  "XrTime predicted display time invalid");
+
+  return XR_SUCCESS;
+}
+
+XrResult OpenXrTestHelper::ValidateXrPosefIsIdentity(
+    const XrPosef& pose) const {
+  XrPosef identity = device::PoseIdentity();
+  bool is_identity = true;
+  is_identity &= pose.orientation.x == identity.orientation.x;
+  is_identity &= pose.orientation.y == identity.orientation.y;
+  is_identity &= pose.orientation.z == identity.orientation.z;
+  is_identity &= pose.orientation.w == identity.orientation.w;
+  is_identity &= pose.position.x == identity.position.x;
+  is_identity &= pose.position.y == identity.position.y;
+  is_identity &= pose.position.z == identity.position.z;
+  RETURN_IF_FALSE(is_identity, XR_ERROR_VALIDATION_FAILURE,
+                  "XrPosef is not an identity");
+
+  return XR_SUCCESS;
+}
diff --git a/device/vr/openxr/test/openxr_test_helper.h b/device/vr/openxr/test/openxr_test_helper.h
new file mode 100644
index 0000000..38e960f4
--- /dev/null
+++ b/device/vr/openxr/test/openxr_test_helper.h
@@ -0,0 +1,98 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_
+#define DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_
+
+#include <d3d11.h>
+#include <unknwn.h>
+#include <wrl.h>
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "device/vr/test/test_hook.h"
+#include "third_party/openxr/include/openxr/openxr.h"
+
+class OpenXrTestHelper : public device::ServiceTestHook {
+ public:
+  OpenXrTestHelper();
+  ~OpenXrTestHelper();
+
+  void TestFailure();
+
+  // TestHookRegistration
+  void SetTestHook(device::VRTestHook* hook) final;
+
+  // Helper methods called by the mock OpenXR runtime. These methods will
+  // call back into the test hook, thus communicating with the test object
+  // on the browser process side.
+  void OnPresentedFrame();
+
+  // Helper methods called by the mock OpenXR runtime to query or set the
+  // state of the runtime.
+
+  XrSystemId GetSystemId();
+  XrSession GetSession();
+  XrSwapchain GetSwapchain();
+  XrSpace CreateLocalSpace();
+  XrSpace CreateViewSpace();
+
+  XrResult BeginSession();
+  XrResult EndSession();
+
+  void SetD3DDevice(ID3D11Device* d3d_device);
+  const std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>>&
+  GetSwapchainTextures() const;
+  uint32_t NextSwapchainImageIndex();
+  XrTime NextPredictedDisplayTime();
+
+  void GetPose(XrPosef* pose);
+
+  // Methods that validate the parameter with the current state of the runtime.
+  XrResult ValidateInstance(XrInstance instance) const;
+  XrResult ValidateSystemId(XrSystemId system_id) const;
+  XrResult ValidateSession(XrSession session) const;
+  XrResult ValidateSwapchain(XrSwapchain swapchain) const;
+  XrResult ValidateSpace(XrSpace space) const;
+  XrResult ValidatePredictedDisplayTime(XrTime time) const;
+  XrResult ValidateXrPosefIsIdentity(const XrPosef& pose) const;
+
+  // Properties of the mock OpenXR runtime that does not change are created
+  // as static variables.
+  static uint32_t NumExtensionsSupported();
+  static uint32_t NumViews();
+  static const char* kExtensions[];
+  static const uint32_t kDimension;
+  static const uint32_t kSwapCount;
+  static const uint32_t kMinSwapchainBuffering;
+  static const uint32_t kMaxViewCount;
+  static const XrViewConfigurationView kViewConfigView;
+  static XrViewConfigurationView kViewConfigurationViews[];
+  static const XrEnvironmentBlendMode kEnvironmentBlendMode;
+
+ private:
+  // Properties of the mock OpenXR runtime that doesn't change throughout the
+  // lifetime of the instance. However, these aren't static because they are
+  // initialized to an invalid value and set to their actual value in their
+  // respective Get*/Create* functions. This allows these variables to be used
+  // to validate that they were queried before being used.
+  XrSystemId system_id_;
+  XrSession session_;
+  XrSwapchain swapchain_;
+  XrSpace local_space_;
+  XrSpace view_space_;
+
+  // Properties that changes depending on the state of the runtime.
+  bool session_running_;
+  Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
+  std::vector<Microsoft::WRL::ComPtr<ID3D11Texture2D>> textures_arr_;
+  uint32_t acquired_swapchain_texture_;
+  uint32_t next_action_space_;
+  XrTime next_predicted_display_time_;
+
+  device::VRTestHook* test_hook_ GUARDED_BY(lock_) = nullptr;
+  base::Lock lock_;
+};
+
+#endif  // DEVICE_VR_OPENXR_TEST_OPENXR_TEST_HELPER_H_