#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/vr/test/mock_xr_device_hook_base.h"
#include "chrome/browser/vr/test/multi_class_browser_test.h"
#include "chrome/browser/vr/test/webxr_vr_browser_test.h"
#include "device/vr/public/cpp/features.h"
#include "device/vr/public/mojom/openxr_interaction_profile_type.mojom.h"
#include "device/vr/public/mojom/test/browser_test_interfaces.mojom.h"
#include "device/vr/test/test_hook.h"
#include "ui/gfx/geometry/decomposed_transform.h"
#include "ui/gfx/geometry/transform.h"
namespace vr {
namespace {
const std::vector<std::string>& GetDefaultOpenXrProfiles() {
static base::NoDestructor<std::vector<std::string>> kDefaultOpenXrProfiles{
{"microsoft-mixed-reality", "windows-mixed-reality",
"generic-trigger-squeeze-touchpad-thumbstick"}};
return *kDefaultOpenXrProfiles;
}
}
void VerifyInputSourceProfilesArray(
WebXrVrBrowserTestBase* t,
const std::vector<std::string>& expected_values) {
t->PollJavaScriptBooleanOrFail(
"isProfileCountEqualTo(" + base::NumberToString(expected_values.size()) +
")",
WebXrVrBrowserTestBase::kPollTimeoutShort);
for (size_t i = 0; i < expected_values.size(); ++i) {
ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(
"isProfileEqualTo(" + base::NumberToString(i) + ", '" +
expected_values[i] + "')"));
}
}
void VerifyInputCounts(WebXrVrBrowserTestBase* t,
uint32_t expected_input_sources,
uint32_t expected_gamepads) {
t->PollJavaScriptBooleanOrFail("inputSourceCount() === " +
base::NumberToString(expected_input_sources));
t->PollJavaScriptBooleanOrFail("inputSourceWithGamepadCount() === " +
base::NumberToString(expected_gamepads));
}
void TestPresentationLocksFocusImpl(WebXrVrBrowserTestBase* t,
std::string filename) {
MockXRDeviceHookBase mock;
t->LoadFileAndAwaitInitialization(filename);
t->EnterSessionWithUserGestureOrFail();
t->ExecuteStepAndWait("stepSetupFocusLoss()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestPresentationLocksFocus) {
TestPresentationLocksFocusImpl(t, "webxr_test_presentation_locks_focus");
}
class WebXrControllerInputMock : public MockXRDeviceHookBase {
public:
void UpdateControllerAndWait(
uint32_t index,
const device::ControllerFrameData& controller_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
UpdateController(index, controller_data);
WaitNumFrames(30);
}
void ToggleButtonTouches(uint32_t index, uint64_t button_mask) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
controller_data.packet_number++;
controller_data.buttons_touched ^= button_mask;
UpdateControllerAndWait(index, controller_data);
}
void ToggleButtons(uint32_t index, uint64_t button_mask) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
controller_data.packet_number++;
controller_data.buttons_pressed ^= button_mask;
controller_data.buttons_touched ^= button_mask;
UpdateControllerAndWait(index, controller_data);
}
void ToggleTriggerButton(uint32_t index, device::XrButtonId button_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
uint64_t button_mask = device::XrButtonMaskFromId(button_id);
controller_data.packet_number++;
controller_data.buttons_pressed ^= button_mask;
controller_data.buttons_touched ^= button_mask;
bool is_pressed = ((controller_data.buttons_pressed & button_mask) != 0);
uint32_t axis_offset = device::XrAxisOffsetFromId(button_id);
DCHECK(controller_data.axis_data[axis_offset].axis_type ==
device::XrAxisType::kTrigger);
controller_data.axis_data[axis_offset].x = is_pressed ? 1.0 : 0.0;
UpdateControllerAndWait(index, controller_data);
}
void SetAxes(uint32_t index, device::XrButtonId button_id, float x, float y) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
uint32_t axis_offset = device::XrAxisOffsetFromId(button_id);
DCHECK(controller_data.axis_data[axis_offset].axis_type != 0);
controller_data.packet_number++;
controller_data.axis_data[axis_offset].x = x;
controller_data.axis_data[axis_offset].y = y;
UpdateControllerAndWait(index, controller_data);
}
void TogglePrimaryTrigger(uint32_t index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
ToggleTriggerButton(index, device::XrButtonId::kAxisTrigger);
}
void PressReleasePrimaryTrigger(uint32_t index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
TogglePrimaryTrigger(index);
TogglePrimaryTrigger(index);
}
void SetControllerPose(uint32_t index,
const gfx::Transform& device_to_origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
controller_data.pose_data = device_to_origin;
UpdateControllerAndWait(index, controller_data);
}
void AssignDefaultHandData(auto& controller_data,
gfx::Quaternion orientation = gfx::Quaternion()) {
gfx::DecomposedTransform decomposed_transform;
decomposed_transform.quaternion = orientation;
auto& joint_data = controller_data.hand_data;
for (uint32_t i = 0; i < std::size(joint_data); i++) {
decomposed_transform.translate[0] = i / 100.0;
joint_data[i] = {static_cast<device::mojom::XRHandJoint>(i),
gfx::Transform::Compose(decomposed_transform),
static_cast<float>(i)};
}
controller_data.has_hand_data = true;
}
void SetDefaultHandData(uint32_t index,
gfx::Quaternion orientation = gfx::Quaternion()) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
AssignDefaultHandData(controller_data, orientation);
UpdateControllerAndWait(index, controller_data);
}
void ClearHandData(uint32_t index) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(index);
controller_data.has_hand_data = false;
UpdateControllerAndWait(index, controller_data);
}
uint32_t CreateAndConnectMinimalGamepad(
device::ControllerRole role =
device::ControllerRole::kControllerRoleRight) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
uint64_t supported_buttons =
device::XrButtonMaskFromId(device::XrButtonId::kAxisTrigger);
std::map<device::XrButtonId, uint32_t> axis_types = {
{device::XrButtonId::kAxisTrigger, device::XrAxisType::kTrigger},
};
return CreateAndConnectController(role, axis_types, supported_buttons);
}
uint32_t CreateAndConnectController(
device::ControllerRole role,
std::map<device::XrButtonId, uint32_t> axis_types = {},
uint64_t supported_buttons = UINT64_MAX) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller = CreateValidController(role);
controller.supported_buttons = supported_buttons;
for (const auto& axis_type : axis_types) {
uint32_t axis_offset = device::XrAxisOffsetFromId(axis_type.first);
controller.axis_data[axis_offset].axis_type = axis_type.second;
}
return ConnectController(controller);
}
void UpdateControllerSupport(
uint32_t controller_index,
const std::map<device::XrButtonId, uint32_t>& axis_types,
uint64_t supported_buttons) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(controller_index);
for (uint32_t i = 0; i < device::kMaxNumAxes; i++) {
auto button_id = GetAxisId(i);
auto it = axis_types.find(button_id);
uint32_t new_axis_type = device::XrAxisType::kNone;
if (it != axis_types.end())
new_axis_type = it->second;
controller_data.axis_data[i].axis_type = new_axis_type;
}
controller_data.supported_buttons = supported_buttons;
UpdateControllerAndWait(controller_index, controller_data);
}
void UpdateControllerRole(uint32_t controller_index,
device::ControllerRole role) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
auto controller_data = GetCurrentControllerData(controller_index);
controller_data.role = role;
UpdateControllerAndWait(controller_index, controller_data);
}
void UpdateInteractionProfile(
device::mojom::OpenXrInteractionProfileType new_profile) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
device_test::mojom::EventData data = {};
data.type = device_test::mojom::EventType::kInteractionProfileChanged;
data.interaction_profile = new_profile;
PopulateEvent(std::move(data));
}
uint32_t CreateVoiceController() {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
return CreateAndConnectMinimalGamepad(
device::ControllerRole::kControllerRoleVoice);
}
private:
device::XrButtonId GetAxisId(uint32_t offset) {
return static_cast<device::XrButtonId>(device::XrButtonId::kAxisTrackpad +
offset);
}
device::ControllerFrameData GetCurrentControllerData(uint32_t index) {
base::AutoLock lock(lock_);
auto iter = controller_data_map_.find(index);
CHECK(iter != controller_data_map_.end());
return iter->second;
}
};
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputHandednessChange) {
WebXrControllerInputMock my_mock;
uint32_t controller_index = my_mock.CreateAndConnectMinimalGamepad();
t->LoadFileAndAwaitInitialization("test_webxr_input_same_object");
t->EnterSessionWithUserGestureOrFail();
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateInputSourceLength(1)");
t->RunJavaScriptOrFail("updateCachedInputSource(0)");
my_mock.UpdateControllerRole(controller_index,
device::ControllerRole::kControllerRoleLeft);
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 2",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateCachedSourcePresence(false)");
t->RunJavaScriptOrFail("validateInputSourceLength(1)");
t->RunJavaScriptOrFail("updateCachedInputSource(0)");
my_mock.UpdateControllerRole(controller_index,
device::ControllerRole::kControllerRoleRight);
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 3",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateCachedSourcePresence(false)");
t->RunJavaScriptOrFail("validateInputSourceLength(1)");
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputSourcesChange) {
WebXrControllerInputMock my_mock;
uint64_t insufficient_buttons =
device::XrButtonMaskFromId(device::XrButtonId::kGrip);
std::map<device::XrButtonId, uint32_t> insufficient_axis_types = {};
uint32_t controller_index = my_mock.CreateAndConnectController(
device::ControllerRole::kControllerRoleRight, insufficient_axis_types,
insufficient_buttons);
t->LoadFileAndAwaitInitialization("test_webxr_input_sources_change_event");
t->EnterSessionWithUserGestureOrFail();
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateAdded(1)");
t->RunJavaScriptOrFail("validateRemoved(0)");
t->RunJavaScriptOrFail("updateCachedInputSource(0)");
t->RunJavaScriptOrFail("validateCachedAddedPresence(true)");
my_mock.DisconnectController(controller_index);
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 2",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateAdded(0)");
t->RunJavaScriptOrFail("validateRemoved(1)");
t->RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
controller_index = my_mock.CreateAndConnectMinimalGamepad();
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 3",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("updateCachedInputSource(0)");
if (t->GetRuntimeType() != XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR) {
my_mock.UpdateControllerSupport(controller_index, insufficient_axis_types,
insufficient_buttons);
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 4",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateAdded(1)");
t->RunJavaScriptOrFail("validateRemoved(1)");
t->RunJavaScriptOrFail("validateCachedAddedPresence(false)");
t->RunJavaScriptOrFail("validateCachedRemovedPresence(true)");
}
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadMinimumData) {
WebXrControllerInputMock my_mock;
uint32_t controller_index = my_mock.CreateAndConnectMinimalGamepad();
t->LoadFileAndAwaitInitialization("test_webxr_gamepad_support");
t->EnterSessionWithUserGestureOrFail();
VerifyInputCounts(t, 1, 1);
std::string button_count = "1";
if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR)
button_count = "4";
t->PollJavaScriptBooleanOrFail("isButtonCountEqualTo(" + button_count + ")",
WebXrVrBrowserTestBase::kPollTimeoutShort);
my_mock.TogglePrimaryTrigger(controller_index);
t->PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR) {
VerifyInputSourceProfilesArray(t, GetDefaultOpenXrProfiles());
}
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestMultipleGamepads) {
WebXrControllerInputMock my_mock;
uint32_t controller_index1 = my_mock.CreateAndConnectMinimalGamepad(
device::ControllerRole::kControllerRoleLeft);
uint32_t controller_index2 = my_mock.CreateAndConnectMinimalGamepad();
t->LoadFileAndAwaitInitialization("test_webxr_gamepad_support");
t->EnterSessionWithUserGestureOrFail();
VerifyInputCounts(t, 2, 2);
std::string button_count = "1";
if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR)
button_count = "4";
ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail("isButtonCountEqualTo(" +
button_count + ", 0)"));
ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail("isButtonCountEqualTo(" +
button_count + ", 1)"));
ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(
"isMappingEqualTo('xr-standard', 0)"));
ASSERT_TRUE(t->RunJavaScriptAndExtractBoolOrFail(
"isMappingEqualTo('xr-standard', 1)"));
my_mock.TogglePrimaryTrigger(controller_index1);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true, 0)");
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, false, 1)");
my_mock.TogglePrimaryTrigger(controller_index2);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true, 1)");
my_mock.TogglePrimaryTrigger(controller_index2);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, false, 1)");
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, true, 0)");
my_mock.TogglePrimaryTrigger(controller_index1);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(0, false, 0)");
if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR) {
VerifyInputSourceProfilesArray(t, GetDefaultOpenXrProfiles());
}
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestGamepadCompleteData) {
WebXrControllerInputMock my_mock;
uint64_t supported_buttons =
device::XrButtonMaskFromId(device::XrButtonId::kAxisTrigger) |
device::XrButtonMaskFromId(device::XrButtonId::kAxisTrackpad) |
device::XrButtonMaskFromId(device::XrButtonId::kAxisThumbstick) |
device::XrButtonMaskFromId(device::XrButtonId::kGrip);
std::map<device::XrButtonId, uint32_t> axis_types = {
{device::XrButtonId::kAxisTrackpad, device::XrAxisType::kTrackpad},
{device::XrButtonId::kAxisTrigger, device::XrAxisType::kTrigger},
{device::XrButtonId::kAxisThumbstick, device::XrAxisType::kJoystick},
};
uint32_t controller_index = my_mock.CreateAndConnectController(
device::ControllerRole::kControllerRoleRight, axis_types,
supported_buttons);
t->LoadFileAndAwaitInitialization("test_webxr_gamepad_support");
t->EnterSessionWithUserGestureOrFail();
VerifyInputCounts(t, 1, 1);
my_mock.SetAxes(controller_index, device::XrButtonId::kAxisTrackpad, 0.25,
-0.25);
my_mock.ToggleButtonTouches(
controller_index,
device::XrButtonMaskFromId(device::XrButtonId::kAxisTrackpad));
my_mock.SetAxes(controller_index, device::XrButtonId::kAxisThumbstick, 0.67,
-0.67);
my_mock.ToggleButtons(
controller_index,
device::XrButtonMaskFromId(device::XrButtonId::kAxisThumbstick));
my_mock.ToggleButtons(controller_index,
device::XrButtonMaskFromId(device::XrButtonId::kGrip));
t->PollJavaScriptBooleanOrFail("isMappingEqualTo('xr-standard')",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonCountEqualTo(4)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(0, 0.25, -0.25)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("areAxesValuesEqualTo(1, 0.67, -0.67)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(1, true)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(2, false)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonTouchedEqualTo(2, true)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonPressedEqualTo(3, true)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->PollJavaScriptBooleanOrFail("isButtonTouchedEqualTo(3, true)",
WebXrVrBrowserTestBase::kPollTimeoutShort);
if (t->GetRuntimeType() == XrBrowserTestBase::RuntimeType::RUNTIME_OPENXR) {
VerifyInputSourceProfilesArray(t, GetDefaultOpenXrProfiles());
}
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInteractionProfileChanged) {
WebXrControllerInputMock my_mock;
uint64_t supported_buttons =
device::XrButtonMaskFromId(device::XrButtonId::kAxisTrigger) |
device::XrButtonMaskFromId(device::XrButtonId::kAxisTrackpad) |
device::XrButtonMaskFromId(device::XrButtonId::kAxisThumbstick) |
device::XrButtonMaskFromId(device::XrButtonId::kGrip);
std::map<device::XrButtonId, uint32_t> axis_types = {
{device::XrButtonId::kAxisTrackpad, device::XrAxisType::kTrackpad},
{device::XrButtonId::kAxisTrigger, device::XrAxisType::kTrigger},
{device::XrButtonId::kAxisThumbstick, device::XrAxisType::kJoystick},
};
my_mock.CreateAndConnectController(
device::ControllerRole::kControllerRoleRight, axis_types,
supported_buttons);
t->LoadFileAndAwaitInitialization("test_webxr_input_same_object");
t->EnterSessionWithUserGestureOrFail();
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateInputSourceLength(1)");
t->RunJavaScriptOrFail("updateCachedInputSource(0)");
my_mock.UpdateInteractionProfile(
device::mojom::OpenXrInteractionProfileType::kKHRSimple);
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 2",
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("validateInputSourceLength(1)");
t->RunJavaScriptOrFail("validateCachedSourcePresence(false)");
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
constexpr device::mojom::OpenXrInteractionProfileType
kInitialInteractionProfile =
device::mojom::OpenXrInteractionProfileType::kMinValue;
static_assert(kInitialInteractionProfile !=
device::mojom::OpenXrInteractionProfileType::kInvalid,
"TestAllKnownInteractionProfileTypes expects the 0th profile in "
"OpenXrInteractionProfileType to be valid.");
constexpr device::mojom::OpenXrInteractionProfileType
kSkippedInteractionProfiles[] = {
device::mojom::OpenXrInteractionProfileType::kInvalid,
device::mojom::OpenXrInteractionProfileType::kMetaHandAim,
};
void TestHandProfiles(WebXrVrBrowserTestBase* t, bool joint_support) {
WebXrControllerInputMock my_mock;
my_mock.UpdateInteractionProfile(
device::mojom::OpenXrInteractionProfileType::kExtHand);
auto controller_data = my_mock.CreateValidController(
device::ControllerRole::kControllerRoleRight);
my_mock.ConnectController(controller_data);
t->LoadFileAndAwaitInitialization("test_webxr_profiles");
if (joint_support) {
t->RunJavaScriptOrFail("setupImmersiveSessionToRequestHands()");
}
t->EnterSessionWithUserGestureOrFail();
t->PollJavaScriptBooleanOrFail("inputChangeEvents === 1",
WebXrVrBrowserTestBase::kPollTimeoutShort);
std::string expected_string =
joint_support ? "generic-hand" : "generic-fixed-hand";
std::string unexpected_string =
joint_support ? "generic-fixed-hand" : "generic-hand";
t->RunJavaScriptOrFail("validateAllInputSourcesContainProfile('" +
expected_string + "')");
t->RunJavaScriptOrFail("validateNoInputSourcesContainProfile('" +
unexpected_string + "')");
}
IN_PROC_BROWSER_TEST_F(WebXrVrOpenXrBrowserTest, TestProfilesHandJoint) {
TestHandProfiles(this, true);
}
IN_PROC_BROWSER_TEST_F(WebXrVrOpenXrBrowserTest, TestProfilesFixedHand) {
TestHandProfiles(this, false);
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestAllKnownInteractionProfileTypes) {
WebXrControllerInputMock my_mock;
my_mock.UpdateInteractionProfile(kInitialInteractionProfile);
auto controller_data = my_mock.CreateValidController(
device::ControllerRole::kControllerRoleRight);
my_mock.ConnectController(controller_data);
t->LoadFileAndAwaitInitialization("test_webxr_input_sources_change_event");
t->EnterSessionWithUserGestureOrFail();
uint32_t expected_change_events = 1;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
static uint32_t kFinalValue = static_cast<uint32_t>(
device::mojom::OpenXrInteractionProfileType::kMaxValue);
static uint32_t kFirstChangedProfileIndex =
static_cast<uint32_t>(kInitialInteractionProfile) + 1;
for (uint32_t i = kFirstChangedProfileIndex; i <= kFinalValue; i++) {
auto profile = static_cast<device::mojom::OpenXrInteractionProfileType>(i);
if (base::Contains(kSkippedInteractionProfiles, profile)) {
continue;
}
my_mock.UpdateInteractionProfile(profile);
expected_change_events++;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
}
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestInputNotVisibleWhenBlurred) {
WebXrControllerInputMock my_mock;
my_mock.CreateAndConnectMinimalGamepad();
t->LoadFileAndAwaitInitialization("test_webxr_input_visibility");
t->EnterSessionWithUserGestureOrFail();
t->PollJavaScriptBooleanOrFail("checkVisibilityState('visible')");
my_mock.WaitNumFrames(1);
t->RunJavaScriptOrFail("validateInputSourceVisible()");
device_test::mojom::EventData event_data = {};
event_data.type = device_test::mojom::EventType::kVisibilityVisibleBlurred;
my_mock.PopulateEvent(event_data);
t->PollJavaScriptBooleanOrFail("checkVisibilityState('visible-blurred')");
t->PollJavaScriptBooleanOrFail("checkInputSourceCount(0)");
t->RunJavaScriptOrFail("validateNullInputPoses()");
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestMultipleControllerInputRegistered) {
WebXrControllerInputMock my_mock;
uint32_t controller_index1 = my_mock.CreateAndConnectMinimalGamepad(
device::ControllerRole::kControllerRoleLeft);
uint32_t controller_index2 = my_mock.CreateAndConnectMinimalGamepad();
t->LoadFileAndAwaitInitialization("test_webxr_input");
t->EnterSessionWithUserGestureOrFail();
t->RunJavaScriptOrFail("stepSetupListeners(2)");
t->RunJavaScriptOrFail("expectedInputSourceIndex = 0");
my_mock.PressReleasePrimaryTrigger(controller_index1);
t->WaitOnJavaScriptStep();
t->RunJavaScriptOrFail("expectedInputSourceIndex = 1");
my_mock.PressReleasePrimaryTrigger(controller_index2);
t->WaitOnJavaScriptStep();
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerInputRegistered) {
WebXrControllerInputMock my_mock;
uint32_t controller_index = my_mock.CreateAndConnectMinimalGamepad();
t->LoadFileAndAwaitInitialization("test_webxr_input");
t->EnterSessionWithUserGestureOrFail();
uint32_t num_iterations = 5;
t->RunJavaScriptOrFail("stepSetupListeners(" +
base::NumberToString(num_iterations) + ")");
for (uint32_t i = 0; i < num_iterations; ++i) {
my_mock.PressReleasePrimaryTrigger(controller_index);
t->WaitOnJavaScriptStep();
}
t->EndTest();
}
std::string TransformToColMajorString(const gfx::Transform& t) {
std::array<float, 16> array;
t.GetColMajorF(array);
std::string array_string = "[";
for (const auto& val : array) {
array_string += base::NumberToString(val) + ",";
}
array_string.pop_back();
array_string.push_back(']');
return array_string;
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestControllerPositionTracking) {
WebXrControllerInputMock my_mock;
auto controller_data = my_mock.CreateValidController(
device::ControllerRole::kControllerRoleRight);
uint32_t controller_index = my_mock.ConnectController(controller_data);
t->LoadFileAndAwaitInitialization("webxr_test_controller_poses");
t->EnterSessionWithUserGestureOrFail();
auto pose = gfx::Transform();
pose.RotateAboutXAxis(90);
pose.RotateAboutYAxis(45);
pose.RotateAboutZAxis(180);
pose.Translate3d(0.5f, 2, -3);
my_mock.SetControllerPose(controller_index, pose);
pose.Translate3d(t->GetControllerOffset());
t->ExecuteStepAndWait("stepWaitForMatchingPose(" +
base::NumberToString(controller_index) + ", " +
TransformToColMajorString(pose) + ")");
t->AssertNoJavaScriptErrors();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestHandDataNotVisibleWithoutFeature) {
WebXrControllerInputMock my_mock;
auto controller_data = my_mock.CreateValidController(
device::ControllerRole::kControllerRoleRight);
my_mock.AssignDefaultHandData(controller_data);
my_mock.ConnectController(controller_data);
t->LoadFileAndAwaitInitialization("test_webxr_hand_tracking");
t->EnterSessionWithUserGestureOrFail();
uint32_t expected_change_events = 1;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("assertHandTrackingFeatureState(false)");
t->RunJavaScriptOrFail("assertHandsNotPresent()");
t->AssertNoJavaScriptErrors();
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestHandDataVisibleWithFeature) {
WebXrControllerInputMock my_mock;
auto controller_data = my_mock.CreateValidController(
device::ControllerRole::kControllerRoleRight);
my_mock.AssignDefaultHandData(controller_data);
my_mock.ConnectController(controller_data);
t->LoadFileAndAwaitInitialization("test_webxr_hand_tracking");
t->RunJavaScriptOrFail("setupRequestHandTracking()");
t->EnterSessionWithUserGestureOrFail();
uint32_t expected_change_events = 1;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("assertHandTrackingFeatureState(true)");
t->RunJavaScriptOrFail("assertHandsPresent()");
t->AssertNoJavaScriptErrors();
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestHandDataVisibleToggle) {
WebXrControllerInputMock my_mock;
auto controller_data = my_mock.CreateValidController(
device::ControllerRole::kControllerRoleRight);
uint32_t index = my_mock.ConnectController(controller_data);
t->LoadFileAndAwaitInitialization("test_webxr_hand_tracking");
t->RunJavaScriptOrFail("setupRequestHandTracking()");
t->EnterSessionWithUserGestureOrFail();
uint32_t expected_change_events = 1;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("assertHandTrackingFeatureState(true)");
t->RunJavaScriptOrFail("assertHandsNotPresent()");
my_mock.SetDefaultHandData(index);
expected_change_events++;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("assertHandsPresent()");
my_mock.ClearHandData(index);
expected_change_events++;
t->PollJavaScriptBooleanOrFail(
"inputChangeEvents === " + base::NumberToString(expected_change_events),
WebXrVrBrowserTestBase::kPollTimeoutShort);
t->RunJavaScriptOrFail("assertHandsNotPresent()");
t->AssertNoJavaScriptErrors();
t->RunJavaScriptOrFail("done()");
t->EndTest();
}
class WebXrHeadPoseMock : public MockXRDeviceHookBase {
public:
void WaitGetPresentingPose(
device_test::mojom::XRTestHook::WaitGetPresentingPoseCallback callback)
final {
DCHECK_CALLED_ON_VALID_SEQUENCE(mock_device_sequence_);
std::optional<gfx::Transform> pose;
{
base::AutoLock lock(pose_lock);
pose = pose_;
}
std::move(callback).Run(std::move(pose));
}
void SetHeadPose(const gfx::Transform& pose) {
DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_);
base::AutoLock lock(pose_lock);
pose_ = pose;
}
private:
base::Lock pose_lock;
gfx::Transform pose_ GUARDED_BY(pose_lock);
};
WEBXR_VR_ALL_RUNTIMES_BROWSER_TEST_F(TestHeadPosesUpdate) {
WebXrHeadPoseMock my_mock;
t->LoadFileAndAwaitInitialization("webxr_test_head_poses");
t->EnterSessionWithUserGestureOrFail();
auto pose = gfx::Transform();
my_mock.SetHeadPose(pose);
t->RunJavaScriptOrFail("stepWaitForMatchingPose(" +
TransformToColMajorString(pose) + ")");
t->WaitOnJavaScriptStep();
pose.RotateAboutXAxis(90);
pose.Translate3d(2, 3, 4);
my_mock.SetHeadPose(pose);
t->RunJavaScriptOrFail("stepWaitForMatchingPose(" +
TransformToColMajorString(pose) + ")");
t->WaitOnJavaScriptStep();
t->AssertNoJavaScriptErrors();
}
}