// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/exo/ui_lock_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/shell.h"
#include "ash/wm/window_state.h"
#include "base/feature_list.h"
#include "base/test/power_monitor_test.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/login/auth/auth_events_recorder.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/exo/buffer.h"
#include "components/exo/display.h"
#include "components/exo/pointer.h"
#include "components/exo/pointer_constraint_delegate.h"
#include "components/exo/pointer_delegate.h"
#include "components/exo/shell_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_helper.h"
#include "components/exo/test/shell_surface_builder.h"
#include "components/exo/wm_helper.h"
#include "components/fullscreen_control/fullscreen_control_popup.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/class_property.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/animation/animation_test_api.h"
#include "ui/wm/core/window_util.h"
namespace exo {
namespace {
constexpr char kNoEscHoldAppId[] = "no-esc-hold";
constexpr char kOverviewToExitAppId[] = "overview-to-exit";
aura::Window* GetTopLevelWindow(
const std::unique_ptr<ShellSurface>& shell_surface) {
auto* top_level_widget = views::Widget::GetTopLevelWidgetForNativeView(
shell_surface->host_window());
assert(top_level_widget);
return top_level_widget->GetNativeWindow();
}
ash::WindowState* GetTopLevelWindowState(
const std::unique_ptr<ShellSurface>& shell_surface) {
return ash::WindowState::Get(GetTopLevelWindow(shell_surface));
}
class MockPointerDelegate : public PointerDelegate {
public:
MockPointerDelegate() {
EXPECT_CALL(*this, CanAcceptPointerEventsForSurface(testing::_))
.WillRepeatedly(testing::Return(true));
}
// Overridden from PointerDelegate:
MOCK_METHOD1(OnPointerDestroying, void(Pointer*));
MOCK_CONST_METHOD1(CanAcceptPointerEventsForSurface, bool(Surface*));
MOCK_METHOD3(OnPointerEnter, void(Surface*, const gfx::PointF&, int));
MOCK_METHOD1(OnPointerLeave, void(Surface*));
MOCK_METHOD2(OnPointerMotion, void(base::TimeTicks, const gfx::PointF&));
MOCK_METHOD3(OnPointerButton, void(base::TimeTicks, int, bool));
MOCK_METHOD3(OnPointerScroll,
void(base::TimeTicks, const gfx::Vector2dF&, bool));
MOCK_METHOD1(OnFingerScrollStop, void(base::TimeTicks));
MOCK_METHOD0(OnPointerFrame, void());
};
class MockPointerConstraintDelegate : public PointerConstraintDelegate {
public:
MockPointerConstraintDelegate(Pointer* pointer, Surface* surface)
: pointer_(pointer) {
EXPECT_CALL(*this, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface));
ON_CALL(*this, OnConstraintActivated).WillByDefault([this]() {
activated_count++;
});
ON_CALL(*this, OnConstraintBroken).WillByDefault([this]() {
broken_count++;
});
}
~MockPointerConstraintDelegate() override {
// Notifying destruction here removes some boilerplate from tests.
pointer_->OnPointerConstraintDelegateDestroying(this);
}
// Overridden from PointerConstraintDelegate:
MOCK_METHOD0(OnConstraintActivated, void());
MOCK_METHOD0(OnAlreadyConstrained, void());
MOCK_METHOD0(OnConstraintBroken, void());
MOCK_METHOD0(IsPersistent, bool());
MOCK_METHOD0(GetConstrainedSurface, Surface*());
MOCK_METHOD0(OnDefunct, void());
raw_ptr<Pointer> pointer_;
int activated_count = 0;
int broken_count = 0;
};
class UILockControllerTest : public test::ExoTestBase {
public:
UILockControllerTest()
: test::ExoTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~UILockControllerTest() override = default;
UILockControllerTest(const UILockControllerTest&) = delete;
UILockControllerTest& operator=(const UILockControllerTest&) = delete;
protected:
class TestPropertyResolver : public exo::WMHelper::AppPropertyResolver {
public:
TestPropertyResolver() = default;
~TestPropertyResolver() override = default;
void PopulateProperties(
const Params& params,
ui::PropertyHandler& out_properties_container) override {
out_properties_container.SetProperty(
chromeos::kEscHoldToExitFullscreen,
params.app_id != kNoEscHoldAppId &&
params.app_id != kOverviewToExitAppId);
out_properties_container.SetProperty(
chromeos::kUseOverviewToExitFullscreen,
params.app_id == kOverviewToExitAppId);
out_properties_container.SetProperty(
chromeos::kUseOverviewToExitPointerLock,
params.app_id == kOverviewToExitAppId);
}
};
// test::ExoTestBase:
void SetUp() override {
test::ExoTestBase::SetUp();
seat_ = std::make_unique<Seat>();
// Order of window activations and observer callbacks is not trivial, e.g.
// lock screen widget is active when `OnLockStateChanged(locked=false)`
// callback is called. It's better to test them with views.
// `AuthEventsRecorder` is required for `set_show_lock_screen_views=true`.
auth_events_recorder_ = ash::AuthEventsRecorder::CreateForTesting();
GetSessionControllerClient()->set_show_lock_screen_views(true);
WMHelper::GetInstance()->RegisterAppPropertyResolver(
std::make_unique<TestPropertyResolver>());
}
void TearDown() override {
seat_.reset();
test::ExoTestBase::TearDown();
}
std::unique_ptr<ShellSurface> BuildSurface(gfx::Point origin, int w, int h) {
test::ShellSurfaceBuilder builder({w, h});
builder.SetOrigin(origin);
return builder.BuildShellSurface();
}
std::unique_ptr<ShellSurface> BuildSurface(int w, int h) {
return BuildSurface(gfx::Point(0, 0), w, h);
}
views::Widget* GetEscNotification(
const std::unique_ptr<ShellSurface>& surface) {
return seat_->GetUILockControllerForTesting()->GetEscNotificationForTesting(
GetTopLevelWindow(surface));
}
views::Widget* GetPointerCaptureNotification(
const std::unique_ptr<ShellSurface>& surface) {
return seat_->GetUILockControllerForTesting()
->GetPointerCaptureNotificationForTesting(GetTopLevelWindow(surface));
}
bool IsExitPopupVisible(aura::Window* window) {
FullscreenControlPopup* popup =
seat_->GetUILockControllerForTesting()->GetExitPopupForTesting(window);
if (popup && popup->IsAnimating()) {
gfx::AnimationTestApi animation_api(popup->GetAnimationForTesting());
base::TimeTicks now = base::TimeTicks::Now();
animation_api.SetStartTime(now);
animation_api.Step(now + base::Milliseconds(500));
}
return popup && popup->IsVisible();
}
std::unique_ptr<Seat> seat_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ash::AuthEventsRecorder> auth_events_recorder_;
};
TEST_F(UILockControllerTest, HoldingEscapeExitsFullscreen) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(600, 400);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
auto* window_state = GetTopLevelWindowState(test_surface);
EXPECT_TRUE(window_state->IsFullscreen());
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(1));
EXPECT_TRUE(window_state->IsFullscreen()); // no change yet
task_environment()->FastForwardBy(base::Seconds(1));
EXPECT_FALSE(window_state->IsFullscreen());
EXPECT_TRUE(window_state->IsNormalStateType());
}
TEST_F(UILockControllerTest, HoldingCtrlEscapeDoesNotExitFullscreen) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
auto* window_state = GetTopLevelWindowState(test_surface);
EXPECT_TRUE(window_state->IsFullscreen());
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_CONTROL_DOWN);
task_environment()->FastForwardBy(base::Seconds(2));
EXPECT_TRUE(window_state->IsFullscreen());
}
TEST_F(UILockControllerTest,
HoldingEscapeOnlyExitsFullscreenIfWindowPropertySet) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
// Do not set chromeos::kEscHoldToExitFullscreen on TopLevelWindow.
test_surface->SetApplicationId(kNoEscHoldAppId);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
auto* window_state = GetTopLevelWindowState(test_surface);
EXPECT_TRUE(window_state->IsFullscreen());
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(2));
EXPECT_TRUE(window_state->IsFullscreen());
}
TEST_F(UILockControllerTest, HoldingEscapeOnlyExitsFocusedFullscreen) {
std::unique_ptr<ShellSurface> test_surface1 = BuildSurface(1024, 768);
test_surface1->SetUseImmersiveForFullscreen(false);
test_surface1->SetFullscreen(true, display::kInvalidDisplayId);
test_surface1->surface_for_testing()->Commit();
std::unique_ptr<ShellSurface> test_surface2 = BuildSurface(1024, 768);
test_surface2->SetUseImmersiveForFullscreen(false);
test_surface2->SetFullscreen(true, display::kInvalidDisplayId);
test_surface2->surface_for_testing()->Commit();
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(2));
EXPECT_TRUE(GetTopLevelWindowState(test_surface1)->IsFullscreen());
EXPECT_FALSE(GetTopLevelWindowState(test_surface2)->IsFullscreen());
}
TEST_F(UILockControllerTest, DestroyingWindowCancels) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
auto* window_state = GetTopLevelWindowState(test_surface);
EXPECT_TRUE(window_state->IsFullscreen());
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(1));
test_surface.reset(); // Destroying the Surface destroys the Window
task_environment()->FastForwardBy(base::Seconds(3));
// The implicit assertion is that the code doesn't crash.
}
TEST_F(UILockControllerTest, FocusChangeCancels) {
// Arrange: two windows, one is fullscreen and focused
std::unique_ptr<ShellSurface> other_surface = BuildSurface(1024, 768);
other_surface->surface_for_testing()->Commit();
std::unique_ptr<ShellSurface> fullscreen_surface = BuildSurface(1024, 768);
fullscreen_surface->SetUseImmersiveForFullscreen(false);
fullscreen_surface->SetFullscreen(true, display::kInvalidDisplayId);
fullscreen_surface->surface_for_testing()->Commit();
EXPECT_EQ(fullscreen_surface->surface_for_testing(),
seat_->GetFocusedSurface());
EXPECT_FALSE(GetTopLevelWindowState(fullscreen_surface)->IsMinimized());
// Act: Press escape, then toggle focus back and forth
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(1));
wm::ActivateWindow(other_surface->surface_for_testing()->window());
wm::ActivateWindow(fullscreen_surface->surface_for_testing()->window());
task_environment()->FastForwardBy(base::Seconds(2));
// Assert: Fullscreen window was not minimized, despite regaining focus.
EXPECT_FALSE(GetTopLevelWindowState(fullscreen_surface)->IsMinimized());
EXPECT_EQ(fullscreen_surface->surface_for_testing(),
seat_->GetFocusedSurface());
}
TEST_F(UILockControllerTest, ShortHoldEscapeDoesNotExitFullscreen) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
auto* window_state = GetTopLevelWindowState(test_surface);
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(1));
GetEventGenerator()->ReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(2));
EXPECT_TRUE(window_state->IsFullscreen());
}
TEST_F(UILockControllerTest, FullScreenShowsEscNotification) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_TRUE(GetTopLevelWindowState(test_surface)->IsFullscreen());
EXPECT_TRUE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, EscNotificationClosesAfterDuration) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_TRUE(GetEscNotification(test_surface));
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, HoldingEscapeHidesNotification) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_TRUE(GetTopLevelWindowState(test_surface)->IsFullscreen());
EXPECT_TRUE(GetEscNotification(test_surface));
GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
task_environment()->FastForwardBy(base::Seconds(3));
EXPECT_FALSE(GetTopLevelWindowState(test_surface)->IsFullscreen());
EXPECT_FALSE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, LosingFullscreenHidesNotification) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_TRUE(GetTopLevelWindowState(test_surface)->IsFullscreen());
EXPECT_TRUE(GetEscNotification(test_surface));
// Have surface loose fullscreen, notification should now be hidden.
test_surface->Minimize();
test_surface->SetFullscreen(false, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_FALSE(GetTopLevelWindowState(test_surface)->IsFullscreen());
EXPECT_FALSE(
seat_->GetUILockControllerForTesting()->GetEscNotificationForTesting(
GetTopLevelWindow(test_surface)));
}
TEST_F(UILockControllerTest, EscNotificationIsReshownIfInterrupted) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_TRUE(GetEscNotification(test_surface));
// Stop fullscreen.
test_surface->SetFullscreen(false, display::kInvalidDisplayId);
EXPECT_FALSE(
seat_->GetUILockControllerForTesting()->GetEscNotificationForTesting(
GetTopLevelWindow(test_surface)));
// Fullscreen should show notification since it did not stay visible for
// duration.
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
EXPECT_TRUE(GetEscNotification(test_surface));
// After duration, notification should be removed.
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetEscNotification(test_surface));
// Notification is shown after fullscreen toggle.
test_surface->SetFullscreen(false, display::kInvalidDisplayId);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
EXPECT_TRUE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, EscNotificationIsReshownAfterUnlock) {
// Arrange: Go fullscreen and time out the notification.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
task_environment()->FastForwardBy(base::Seconds(10));
// Ensure the notification did time out; if not, we can't trust the test
// result.
EXPECT_FALSE(GetEscNotification(test_surface));
// Act: Simulate locking and unlocking.
GetSessionControllerClient()->LockScreen();
GetSessionControllerClient()->UnlockScreen();
// Assert: Notification shown again.
EXPECT_TRUE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, EscNotificationReshownWhenScreenTurnedOn) {
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
task_environment()->FastForwardBy(base::Seconds(10));
// Ensure the notification did time out; if not, we can't trust the test
// result.
EXPECT_FALSE(GetEscNotification(test_surface));
// Act: Simulate turning the backlight off and on again.
power_manager::SetBacklightBrightnessRequest request;
request.set_percent(0);
chromeos::FakePowerManagerClient::Get()->SetScreenBrightness(request);
base::RunLoop().RunUntilIdle();
request.set_percent(100);
chromeos::FakePowerManagerClient::Get()->SetScreenBrightness(request);
base::RunLoop().RunUntilIdle();
// Assert: Notification shown again.
EXPECT_TRUE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, EscNotificationReshownWhenLidReopened) {
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
task_environment()->FastForwardBy(base::Seconds(10));
// Ensure the notification did time out; if not, we can't trust the test
// result.
EXPECT_FALSE(GetEscNotification(test_surface));
// Act: Simulate closing and reopening the lid.
chromeos::FakePowerManagerClient::Get()->SetLidState(
chromeos::PowerManagerClient::LidState::CLOSED, base::TimeTicks::Now());
chromeos::FakePowerManagerClient::Get()->SetLidState(
chromeos::PowerManagerClient::LidState::OPEN, base::TimeTicks::Now());
// Assert: Notification shown again.
EXPECT_TRUE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, EscNotificationShowsOnSecondaryDisplay) {
// Create surface on secondary display.
UpdateDisplay("900x800,70x600");
std::unique_ptr<ShellSurface> test_surface =
BuildSurface(gfx::Point(900, 100), 200, 200);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
// Esc notification should be in secondary display.
views::Widget* esc_notification = GetEscNotification(test_surface);
EXPECT_TRUE(GetSecondaryDisplay().bounds().Contains(
esc_notification->GetWindowBoundsInScreen()));
}
TEST_F(UILockControllerTest, PointerLockShowsNotification) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockNotificationObeysCooldown) {
// Arrange: Set up a pointer capture notification.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
// Act: Wait for the notification to timeout.
task_environment()->FastForwardBy(base::Seconds(5));
// Assert: Notification has disappeared.
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Remove and re-apply the constraint.
pointer.OnPointerConstraintDelegateDestroying(&constraint);
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
// Assert: Notification not shown due to the cooldown.
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Wait for the cooldown, then re-apply again
pointer.OnPointerConstraintDelegateDestroying(&constraint);
task_environment()->FastForwardBy(base::Minutes(5));
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
// Assert: Cooldown has expired so notification is shown.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockNotificationReshownOnLidOpen) {
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Simulate closing and reopening the lid.
chromeos::FakePowerManagerClient::Get()->SetLidState(
chromeos::PowerManagerClient::LidState::CLOSED, base::TimeTicks::Now());
chromeos::FakePowerManagerClient::Get()->SetLidState(
chromeos::PowerManagerClient::LidState::OPEN, base::TimeTicks::Now());
// Assert: Notification shown again.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockNotificationReshownWhenScreenTurnedOn) {
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Simulate turning the backlight off and on again.
power_manager::SetBacklightBrightnessRequest request;
request.set_percent(0);
chromeos::FakePowerManagerClient::Get()->SetScreenBrightness(request);
base::RunLoop().RunUntilIdle();
request.set_percent(100);
chromeos::FakePowerManagerClient::Get()->SetScreenBrightness(request);
base::RunLoop().RunUntilIdle();
// Assert: Notification shown again.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockNotificationReshownOnUnlock) {
// Lock screen takes focus and it disables pointer capture.
GetSessionControllerClient()->set_show_lock_screen_views(false);
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Simulate locking and unlocking.
GetSessionControllerClient()->LockScreen();
GetSessionControllerClient()->UnlockScreen();
// Assert: Notification shown again.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockNotificationReshownAfterSuspend) {
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Simulate suspend and resume
chromeos::FakePowerManagerClient::Get()->SendSuspendImminent(
power_manager::SuspendImminent_Reason_IDLE);
task_environment()->FastForwardBy(base::Minutes(1));
chromeos::FakePowerManagerClient::Get()->SendSuspendDone(base::Minutes(1));
// Assert: Notification shown again.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockNotificationReshownAfterIdle) {
// Arrange: Set up a pointer capture notification, then let it expire.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Simulate activity, then go idle.
seat_->GetUILockControllerForTesting()->OnUserActivity(/*event=*/nullptr);
task_environment()->FastForwardBy(base::Minutes(10));
// Assert: Notification not yet shown again.
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
// Act: Simulate activity after being idle.
seat_->GetUILockControllerForTesting()->OnUserActivity(/*event=*/nullptr);
// Assert: Notification shown again.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, PointerLockCooldownResetForAllWindows) {
// Arrange: Create two surfaces, one with a pointer lock notification.
std::unique_ptr<ShellSurface> other_surface = BuildSurface(1024, 768);
other_surface->SetApplicationId(kOverviewToExitAppId);
other_surface->surface_for_testing()->Commit();
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
// Act: Focus the other window, then lock and unlock.
wm::ActivateWindow(other_surface->surface_for_testing()->window());
GetSessionControllerClient()->LockScreen();
GetSessionControllerClient()->UnlockScreen();
// Assert: Notification shown again.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
}
TEST_F(UILockControllerTest, FullscreenNotificationHasPriority) {
// Arrange: Set up a pointer capture notification.
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->surface_for_testing()->Commit();
testing::NiceMock<MockPointerDelegate> delegate;
Pointer pointer(&delegate, seat_.get());
testing::NiceMock<MockPointerConstraintDelegate> constraint(
&pointer, test_surface->surface_for_testing());
EXPECT_TRUE(pointer.ConstrainPointer(&constraint));
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
// Act: Go fullscreen.
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
// Assert: Fullscreen notification overrides pointer notification.
EXPECT_FALSE(GetPointerCaptureNotification(test_surface));
EXPECT_TRUE(GetEscNotification(test_surface));
// Act: Exit fullscreen.
test_surface->SetFullscreen(false, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
// Assert: Pointer notification returns, since it was interrupted.
EXPECT_TRUE(GetPointerCaptureNotification(test_surface));
EXPECT_FALSE(GetEscNotification(test_surface));
}
TEST_F(UILockControllerTest, ExitPopup) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
auto* window_state = GetTopLevelWindowState(test_surface);
EXPECT_TRUE(window_state->IsFullscreen());
aura::Window* window = GetTopLevelWindow(test_surface);
EXPECT_FALSE(IsExitPopupVisible(window));
EXPECT_TRUE(GetEscNotification(test_surface));
// Move mouse above y=3 should not show exit popup while notification is
// visible.
GetEventGenerator()->MoveMouseTo(0, 2);
EXPECT_FALSE(IsExitPopupVisible(window));
// Wait for notification to close, now exit popup should show.
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(GetEscNotification(test_surface));
GetEventGenerator()->MoveMouseTo(1, 2);
EXPECT_TRUE(IsExitPopupVisible(window));
// Move mouse below y=150 should hide exit popup.
GetEventGenerator()->MoveMouseTo(0, 160);
EXPECT_FALSE(IsExitPopupVisible(window));
// Move mouse back above y=3 should show exit popup.
GetEventGenerator()->MoveMouseTo(0, 2);
EXPECT_TRUE(IsExitPopupVisible(window));
// Popup should hide after 3s.
task_environment()->FastForwardBy(base::Seconds(5));
EXPECT_FALSE(IsExitPopupVisible(window));
// Moving mouse to y=100, then above y=3 should still have popup hidden.
GetEventGenerator()->MoveMouseTo(0, 100);
GetEventGenerator()->MoveMouseTo(0, 2);
EXPECT_FALSE(IsExitPopupVisible(window));
// Moving mouse below y=150, then above y=3 should show exit popup.
GetEventGenerator()->MoveMouseTo(0, 160);
GetEventGenerator()->MoveMouseTo(0, 2);
EXPECT_TRUE(IsExitPopupVisible(window));
// Clicking exit popup should exit fullscreen.
FullscreenControlPopup* popup =
seat_->GetUILockControllerForTesting()->GetExitPopupForTesting(window);
GetEventGenerator()->MoveMouseTo(
popup->GetPopupWidget()->GetWindowBoundsInScreen().CenterPoint());
GetEventGenerator()->ClickLeftButton();
EXPECT_FALSE(window_state->IsFullscreen());
EXPECT_FALSE(IsExitPopupVisible(window));
}
TEST_F(UILockControllerTest, ExitPopupNotShownForOverviewCase) {
std::unique_ptr<ShellSurface> test_surface = BuildSurface(1024, 768);
// Set chromeos::kUseOverviewToExitFullscreen on TopLevelWindow.
test_surface->SetApplicationId(kOverviewToExitAppId);
test_surface->SetUseImmersiveForFullscreen(false);
test_surface->SetFullscreen(true, display::kInvalidDisplayId);
test_surface->surface_for_testing()->Commit();
EXPECT_FALSE(IsExitPopupVisible(GetTopLevelWindow(test_surface)));
// Move mouse above y=3 should not show exit popup.
GetEventGenerator()->MoveMouseTo(0, 2);
EXPECT_FALSE(IsExitPopupVisible(GetTopLevelWindow(test_surface)));
}
TEST_F(UILockControllerTest, OnlyShowWhenActive) {
std::unique_ptr<ShellSurface> test_surface1 = BuildSurface(1024, 768);
test_surface1->surface_for_testing()->Commit();
std::unique_ptr<ShellSurface> test_surface2 =
BuildSurface(gfx::Point(100, 100), 200, 200);
test_surface2->surface_for_testing()->Commit();
// Surface2 is active when we make Surface1 fullscreen.
// Esc notification, and exit popup should not be shown.
test_surface1->SetFullscreen(true, display::kInvalidDisplayId);
EXPECT_FALSE(GetEscNotification(test_surface1));
GetEventGenerator()->MoveMouseTo(0, 2);
EXPECT_FALSE(IsExitPopupVisible(GetTopLevelWindow(test_surface1)));
}
} // namespace
} // namespace exo