blob: 59d0147294a3c05bf6a97dff479417cabc12f598 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/vr/ui_scene_creator.h"
#include "base/numerics/ranges.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/browser/vr/elements/button.h"
#include "chrome/browser/vr/elements/content_element.h"
#include "chrome/browser/vr/elements/disc_button.h"
#include "chrome/browser/vr/elements/indicator_spec.h"
#include "chrome/browser/vr/elements/rect.h"
#include "chrome/browser/vr/elements/repositioner.h"
#include "chrome/browser/vr/elements/ui_element.h"
#include "chrome/browser/vr/elements/ui_element_name.h"
#include "chrome/browser/vr/elements/vector_icon.h"
#include "chrome/browser/vr/model/assets.h"
#include "chrome/browser/vr/model/model.h"
#include "chrome/browser/vr/speech_recognizer.h"
#include "chrome/browser/vr/target_property.h"
#include "chrome/browser/vr/test/animation_utils.h"
#include "chrome/browser/vr/test/constants.h"
#include "chrome/browser/vr/test/mock_ui_browser_interface.h"
#include "chrome/browser/vr/test/ui_test.h"
#include "chrome/browser/vr/ui_renderer.h"
#include "chrome/browser/vr/ui_scene.h"
#include "chrome/browser/vr/ui_scene_constants.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/test/geometry_util.h"
namespace vr {
namespace {
const std::set<UiElementName> kFloorCeilingBackgroundElements = {
kSolidBackground, kCeiling, kFloor,
};
const std::set<UiElementName> kElementsVisibleInBrowsing = {
kSolidBackground,
kCeiling,
kFloor,
kContentFrame,
kContentFrameHitPlane,
kContentQuad,
kBackplane,
kUrlBar,
kUrlBarBackButton,
kUrlBarLeftSeparator,
kUrlBarOriginRegion,
kUrlBarSecurityButton,
kUrlBarUrlText,
kUrlBarRightSeparator,
kUrlBarOverflowButton,
kController,
kLaser,
kControllerTouchpadButton,
kControllerAppButton,
kControllerHomeButton,
kControllerBatteryDot0,
kControllerBatteryDot1,
kControllerBatteryDot2,
kControllerBatteryDot3,
kControllerBatteryDot4,
kIndicatorBackplane,
};
const std::set<UiElementName> kElementsVisibleWithExitWarning = {
kScreenDimmer, kExitWarningBackground, kExitWarningText};
const std::set<UiElementName> kElementsVisibleWithVoiceSearch = {
kSpeechRecognitionListening, kSpeechRecognitionMicrophoneIcon,
kSpeechRecognitionListeningCloseButton, kSpeechRecognitionCircle,
kSpeechRecognitionListeningGrowingCircle};
const std::set<UiElementName> kElementsVisibleWithVoiceSearchResult = {
kSpeechRecognitionResult, kSpeechRecognitionCircle,
kSpeechRecognitionMicrophoneIcon, kSpeechRecognitionResultBackplane};
constexpr float kSmallDelaySeconds = 0.1f;
MATCHER_P2(SizeFsAreApproximatelyEqual, other, tolerance, "") {
return base::IsApproximatelyEqual(arg.width(), other.width(), tolerance) &&
base::IsApproximatelyEqual(arg.height(), other.height(), tolerance);
}
void VerifyNoHitTestableElementInSubtree(UiElement* element) {
EXPECT_FALSE(element->IsHitTestable());
for (auto& child : element->children())
VerifyNoHitTestableElementInSubtree(child.get());
}
} // namespace
TEST_F(UiTest, WebVrToastStateTransitions) {
// Tests toast not showing when directly entering VR though WebVR
// presentation.
CreateScene(kInWebVr);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
CreateScene(kNotInWebVr);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
auto browser_ui = ui_->GetBrowserUiWeakPtr();
browser_ui->SetWebVrMode(true);
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
browser_ui->SetCapturingState(CapturingStateModel(), CapturingStateModel(),
CapturingStateModel());
EXPECT_TRUE(IsVisible(kWebVrExclusiveScreenToast));
browser_ui->SetWebVrMode(false);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
browser_ui->SetWebVrMode(true);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
browser_ui->SetWebVrMode(false);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
}
TEST_F(UiTest, WebVrToastTransience) {
CreateScene(kNotInWebVr);
auto browser_ui = ui_->GetBrowserUiWeakPtr();
browser_ui->SetWebVrMode(true);
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
browser_ui->SetCapturingState(CapturingStateModel(), CapturingStateModel(),
CapturingStateModel());
EXPECT_TRUE(IsVisible(kWebVrExclusiveScreenToast));
EXPECT_TRUE(RunForSeconds(kWindowsInitialIndicatorsTimeoutSeconds +
kSmallDelaySeconds));
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
browser_ui->SetWebVrMode(false);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
}
TEST_F(UiTest, CaptureToasts) {
CreateScene(kNotInWebVr);
auto browser_ui = ui_->GetBrowserUiWeakPtr();
for (auto& spec : GetIndicatorSpecs()) {
for (int i = 0; i < 3; ++i) {
#if !BUILDFLAG(IS_ANDROID)
if (i == 1) // Skip background tabs for non-Android platforms.
continue;
#endif
browser_ui->SetWebVrMode(true);
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
CapturingStateModel active_capturing;
CapturingStateModel background_capturing;
CapturingStateModel potential_capturing;
active_capturing.*spec.signal = i == 0;
// High accuracy location cannot be used in a background tab.
// Also, capturing USB and Midi cannot be done in background tab.
background_capturing.*spec.signal =
i == 1 && spec.name != kLocationAccessIndicator &&
spec.name != kUsbConnectedIndicator &&
spec.name != kMidiConnectedIndicator;
potential_capturing.*spec.signal =
i == 2 && spec.name != kUsbConnectedIndicator;
int string_id = 0;
switch (i) {
case 0:
string_id = spec.resource_string;
break;
case 1:
string_id = spec.background_resource_string;
break;
case 2:
string_id = spec.potential_resource_string;
break;
default:
NOTREACHED();
break;
}
browser_ui->SetCapturingState(active_capturing, background_capturing,
potential_capturing);
EXPECT_TRUE(IsVisible(kWebVrExclusiveScreenToast));
EXPECT_TRUE(IsVisible(spec.webvr_name) == (string_id != 0));
EXPECT_TRUE(RunForSeconds(kWindowsInitialIndicatorsTimeoutSeconds +
kSmallDelaySeconds));
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
browser_ui->SetWebVrMode(false);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
EXPECT_FALSE(IsVisible(spec.webvr_name));
}
}
}
TEST_F(UiTest, UiUpdatesForIncognito) {
CreateScene(kNotInWebVr);
auto browser_ui = ui_->GetBrowserUiWeakPtr();
// Hold onto the background color to make sure it changes.
SkColor initial_background = SK_ColorBLACK;
GetBackgroundColor(&initial_background);
EXPECT_EQ(
ColorScheme::GetColorScheme(ColorScheme::kModeNormal).world_background,
initial_background);
browser_ui->SetFullscreen(true);
// Make sure background has changed for fullscreen.
SkColor fullscreen_background = SK_ColorBLACK;
GetBackgroundColor(&fullscreen_background);
EXPECT_EQ(ColorScheme::GetColorScheme(ColorScheme::kModeFullscreen)
.world_background,
fullscreen_background);
model_->incognito = true;
// Make sure background remains fullscreen colored.
SkColor incognito_background = SK_ColorBLACK;
GetBackgroundColor(&incognito_background);
EXPECT_EQ(ColorScheme::GetColorScheme(ColorScheme::kModeFullscreen)
.world_background,
incognito_background);
model_->incognito = false;
SkColor no_longer_incognito_background = SK_ColorBLACK;
GetBackgroundColor(&no_longer_incognito_background);
EXPECT_EQ(fullscreen_background, no_longer_incognito_background);
browser_ui->SetFullscreen(false);
SkColor no_longer_fullscreen_background = SK_ColorBLACK;
GetBackgroundColor(&no_longer_fullscreen_background);
EXPECT_EQ(initial_background, no_longer_fullscreen_background);
// Incognito, but not fullscreen, should show incognito colors.
model_->incognito = true;
SkColor incognito_again_background = SK_ColorBLACK;
GetBackgroundColor(&incognito_again_background);
EXPECT_EQ(
ColorScheme::GetColorScheme(ColorScheme::kModeIncognito).world_background,
incognito_again_background);
model_->incognito = false;
SkColor no_longer_incognito_again_background = SK_ColorBLACK;
GetBackgroundColor(&no_longer_incognito_again_background);
EXPECT_EQ(initial_background, no_longer_incognito_again_background);
}
TEST_F(UiTest, UiModeWebVr) {
CreateScene(kNotInWebVr);
auto browser_ui = ui_->GetBrowserUiWeakPtr();
EXPECT_EQ(model_->ui_modes.size(), 1u);
EXPECT_EQ(model_->ui_modes.back(), kModeBrowsing);
VerifyOnlyElementsVisible("Initial", kElementsVisibleInBrowsing);
browser_ui->SetWebVrMode(true);
EXPECT_EQ(model_->ui_modes.size(), 2u);
EXPECT_EQ(model_->ui_modes[1], kModeWebVr);
EXPECT_EQ(model_->ui_modes[0], kModeBrowsing);
VerifyOnlyElementsVisible("WebVR", {kWebVrBackground});
browser_ui->SetWebVrMode(false);
EXPECT_EQ(model_->ui_modes.size(), 1u);
EXPECT_EQ(model_->ui_modes.back(), kModeBrowsing);
VerifyOnlyElementsVisible("Browsing after WebVR", kElementsVisibleInBrowsing);
}
TEST_F(UiTest, HostedUiInWebVr) {
CreateScene(kInWebVr);
VerifyVisibility({kWebVrHostedUi, kWebVrFloor}, false);
ui_->SetAlertDialogEnabled(true, nullptr, 0, 0);
AdvanceFrame();
VerifyVisibility({kWebVrHostedUi, kWebVrBackground, kWebVrFloor}, true);
ui_->SetAlertDialogEnabled(false, nullptr, 0, 0);
AdvanceFrame();
VerifyVisibility({kWebVrHostedUi, kWebVrFloor}, false);
}
TEST_F(UiTest, UiUpdatesForWebVR) {
CreateScene(kInWebVr);
model_->active_capturing.audio_capture_enabled = true;
model_->active_capturing.video_capture_enabled = true;
model_->active_capturing.screen_capture_enabled = true;
model_->active_capturing.location_access_enabled = true;
model_->active_capturing.bluetooth_connected = true;
model_->active_capturing.usb_connected = true;
model_->active_capturing.midi_connected = true;
VerifyOnlyElementsVisible("Elements hidden",
std::set<UiElementName>{kWebVrBackground});
}
// This test verifies that we ignore the WebVR frame when we're not expecting
// WebVR presentation. You can get an unexpected frame when for example, the
// user hits the menu button to exit WebVR mode, but the site continues to pump
// frames. If the frame is not ignored, our UI will think we're in WebVR mode.
TEST_F(UiTest, WebVrFramesIgnoredWhenUnexpected) {
CreateScene(kInWebVr);
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
VerifyOnlyElementsVisible("Elements hidden", std::set<UiElementName>{});
// Disable WebVR mode.
ui_->GetBrowserUiWeakPtr()->SetWebVrMode(false);
// New frame available after exiting WebVR mode.
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
VerifyOnlyElementsVisible("Browser visible", kElementsVisibleInBrowsing);
}
TEST_F(UiTest, UiUpdateTransitionToWebVR) {
CreateScene(kNotInWebVr);
model_->active_capturing.audio_capture_enabled = true;
model_->active_capturing.video_capture_enabled = true;
model_->active_capturing.screen_capture_enabled = true;
model_->active_capturing.location_access_enabled = true;
model_->active_capturing.bluetooth_connected = true;
model_->active_capturing.usb_connected = true;
model_->active_capturing.midi_connected = true;
// Transition to WebVR mode
ui_->GetBrowserUiWeakPtr()->SetWebVrMode(true);
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
// All elements should be hidden.
VerifyOnlyElementsVisible("Elements hidden", std::set<UiElementName>{});
}
TEST_F(UiTest, WebVrTimeout) {
CreateScene(kInWebVr);
ui_->GetBrowserUiWeakPtr()->SetWebVrMode(true);
model_->web_vr.state = kWebVrAwaitingFirstFrame;
RunForMs(500);
// On Windows, the timeout message button is not shown.
#if !BUILDFLAG(IS_WIN)
VerifyVisibility(
{kWebVrTimeoutSpinner, kWebVrTimeoutMessage, kWebVrTimeoutMessageLayout,
kWebVrTimeoutMessageIcon, kWebVrTimeoutMessageText,
kWebVrTimeoutMessageButton, kWebVrTimeoutMessageButtonText},
false);
#else
VerifyVisibility(
{kWebVrTimeoutSpinner, kWebVrTimeoutMessage, kWebVrTimeoutMessageLayout,
kWebVrTimeoutMessageIcon, kWebVrTimeoutMessageText},
false);
#endif // !BUILDFLAG(IS_WIN)
VerifyVisibility(
{
kWebVrBackground,
},
true);
model_->web_vr.state = kWebVrTimeoutImminent;
RunForMs(500);
// On Windows, the timeout message button is not shown.
#if !BUILDFLAG(IS_WIN)
VerifyVisibility({kWebVrTimeoutMessage, kWebVrTimeoutMessageLayout,
kWebVrTimeoutMessageIcon, kWebVrTimeoutMessageText,
kWebVrTimeoutMessageButton, kWebVrTimeoutMessageButtonText},
false);
#else
VerifyVisibility({kWebVrTimeoutMessage, kWebVrTimeoutMessageLayout,
kWebVrTimeoutMessageIcon, kWebVrTimeoutMessageText},
false);
#endif // !BUILDFLAG(IS_WIN)
VerifyVisibility(
{
kWebVrTimeoutSpinner, kWebVrBackground,
},
true);
model_->web_vr.state = kWebVrTimedOut;
RunForMs(500);
VerifyVisibility(
{
kWebVrTimeoutSpinner,
},
false);
// On Windows, the timeout message button is not shown.
#if !BUILDFLAG(IS_WIN)
VerifyVisibility(
{kWebVrBackground, kWebVrTimeoutMessage, kWebVrTimeoutMessageLayout,
kWebVrTimeoutMessageIcon, kWebVrTimeoutMessageText,
kWebVrTimeoutMessageButton, kWebVrTimeoutMessageButtonText},
true);
#else
VerifyVisibility(
{kWebVrBackground, kWebVrTimeoutMessage, kWebVrTimeoutMessageLayout,
kWebVrTimeoutMessageIcon, kWebVrTimeoutMessageText},
true);
#endif // !BUILDFLAG(IS_WIN)
}
TEST_F(UiTest, ExitPresentAndFullscreenOnMenuButtonClick) {
CreateScene(kNotInWebVr);
ui_->GetBrowserUiWeakPtr()->SetWebVrMode(true);
// Clicking menu button should trigger to exit presentation.
EXPECT_CALL(*browser_, ExitPresent());
// And also trigger exit fullscreen.
EXPECT_CALL(*browser_, ExitFullscreen());
InputEventList events;
events.push_back(
std::make_unique<InputEvent>(InputEvent::kMenuButtonClicked));
ui_->HandleMenuButtonEvents(&events);
base::RunLoop().RunUntilIdle();
}
TEST_F(UiTest, ResetRepositioner) {
CreateScene(kNotInWebVr);
Repositioner* repositioner = static_cast<Repositioner*>(
scene_->GetUiElementByName(k2dBrowsingRepositioner));
AdvanceFrame();
gfx::Transform original = repositioner->world_space_transform();
repositioner->set_laser_direction(kForwardVector);
repositioner->SetEnabled(true);
repositioner->set_laser_direction({1, 0, 0});
AdvanceFrame();
EXPECT_NE(original, repositioner->world_space_transform());
repositioner->SetEnabled(false);
model_->mutable_primary_controller().recentered = true;
AdvanceFrame();
EXPECT_TRANSFORM_NEAR(original, repositioner->world_space_transform(), 1e-20);
}
TEST_F(UiTest, RepositionHostedUi) {
CreateScene(kNotInWebVr);
Repositioner* repositioner = static_cast<Repositioner*>(
scene_->GetUiElementByName(k2dBrowsingRepositioner));
UiElement* hosted_ui = scene_->GetUiElementByName(k2dBrowsingHostedUi);
model_->hosted_platform_ui.hosted_ui_enabled = true;
AdvanceFrame();
gfx::Transform original = hosted_ui->world_space_transform();
repositioner->set_laser_direction(kForwardVector);
repositioner->SetEnabled(true);
repositioner->set_laser_direction({0, 1, 0});
AdvanceFrame();
EXPECT_NE(original, hosted_ui->world_space_transform());
repositioner->SetEnabled(false);
}
// Ensures that permissions do not appear after showing hosted UI.
TEST_F(UiTest, DoNotShowIndicatorsAfterHostedUi) {
#if !BUILDFLAG(IS_WIN)
CreateScene(kInWebVr);
auto browser_ui = ui_->GetBrowserUiWeakPtr();
browser_ui->SetWebVrMode(true);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
browser_ui->SetCapturingState(CapturingStateModel(), CapturingStateModel(),
CapturingStateModel());
AdvanceFrame();
EXPECT_TRUE(IsVisible(kWebVrExclusiveScreenToast));
RunForSeconds(8);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
model_->web_vr.showing_hosted_ui = true;
AdvanceFrame();
model_->web_vr.showing_hosted_ui = false;
AdvanceFrame();
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
#endif
}
// Ensures that permissions appear on long press, and that when the menu button
// is released that we do not show the exclusive screen toast. Distinguishing
// these cases requires knowledge of the previous state.
TEST_F(UiTest, LongPressMenuButtonInWebVrMode) {
#if !BUILDFLAG(IS_WIN)
CreateScene(kInWebVr);
auto browser_ui = ui_->GetBrowserUiWeakPtr();
browser_ui->SetWebVrMode(true);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
ui_->GetSchedulerUiPtr()->OnWebXrFrameAvailable();
browser_ui->SetCapturingState(CapturingStateModel(), CapturingStateModel(),
CapturingStateModel());
AdvanceFrame();
EXPECT_TRUE(IsVisible(kWebVrExclusiveScreenToast));
RunForSeconds(8);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
model_->active_capturing.audio_capture_enabled = true;
EXPECT_FALSE(model_->menu_button_long_pressed);
InputEventList events;
events.push_back(
std::make_unique<InputEvent>(InputEvent::kMenuButtonLongPressStart));
ui_->HandleMenuButtonEvents(&events);
AdvanceFrame();
EXPECT_TRUE(model_->menu_button_long_pressed);
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
EXPECT_TRUE(IsVisible(kWebVrAudioCaptureIndicator));
RunForSeconds(8);
AdvanceFrame();
EXPECT_TRUE(model_->menu_button_long_pressed);
EXPECT_FALSE(IsVisible(kWebVrAudioCaptureIndicator));
EXPECT_FALSE(IsVisible(kWebVrAudioCaptureIndicator));
EXPECT_FALSE(IsVisible(kWebVrExclusiveScreenToast));
events.push_back(
std::make_unique<InputEvent>(InputEvent::kMenuButtonLongPressEnd));
ui_->HandleMenuButtonEvents(&events);
EXPECT_FALSE(model_->menu_button_long_pressed);
#endif
}
TEST_F(UiTest, SteadyState) {
CreateScene(kNotInWebVr);
RunForSeconds(10.0f);
// Should have reached steady state.
EXPECT_FALSE(AdvanceFrame());
}
} // namespace vr