blob: 3a465893bdbf63aa25814f6944864b84b2f593a5 [file] [log] [blame]
// Copyright 2015 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 "components/exo/pointer.h"
#include "ash/constants/app_types.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/window_positioning_utils.h"
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "build/chromeos_buildflags.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/exo/buffer.h"
#include "components/exo/data_source.h"
#include "components/exo/data_source_delegate.h"
#include "components/exo/pointer_constraint_delegate.h"
#include "components/exo/pointer_delegate.h"
#include "components/exo/pointer_stylus_delegate.h"
#include "components/exo/relative_pointer_delegate.h"
#include "components/exo/seat.h"
#include "components/exo/shell_surface.h"
#include "components/exo/sub_surface.h"
#include "components/exo/surface.h"
#include "components/exo/test/exo_test_base.h"
#include "components/exo/test/exo_test_data_exchange_delegate.h"
#include "components/exo/test/exo_test_helper.h"
#include "components/exo/test/mock_security_delegate.h"
#include "components/exo/test/shell_surface_builder.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "ash/drag_drop/drag_drop_controller.h"
#include "base/test/bind.h"
#include "components/exo/wm_helper.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/test/events_test_utils.h"
#endif
using ::testing::_;
using ::testing::AnyNumber;
namespace exo {
namespace {
void DispatchGesture(ui::EventType gesture_type, gfx::Point location) {
ui::GestureEventDetails event_details(gesture_type);
ui::GestureEvent gesture_event(location.x(), location.y(), 0,
ui::EventTimeForNow(), event_details);
ui::EventSource* event_source =
ash::Shell::GetPrimaryRootWindow()->GetHost()->GetEventSource();
ui::EventSourceTestApi event_source_test(event_source);
ui::EventDispatchDetails details =
event_source_test.SendEventToSink(&gesture_event);
CHECK(!details.dispatcher_destroyed);
}
class MockPointerDelegate : public PointerDelegate {
public:
MockPointerDelegate() {}
// 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(OnPointerScrollStop, void(base::TimeTicks));
MOCK_METHOD0(OnPointerFrame, void());
};
class MockRelativePointerDelegate : public RelativePointerDelegate {
public:
MockRelativePointerDelegate() = default;
~MockRelativePointerDelegate() = default;
// Overridden from RelativePointerDelegate:
MOCK_METHOD1(OnPointerDestroying, void(Pointer*));
MOCK_METHOD3(OnPointerRelativeMotion,
void(base::TimeTicks,
const gfx::Vector2dF&,
const gfx::Vector2dF&));
};
class MockPointerConstraintDelegate : public PointerConstraintDelegate {
public:
MockPointerConstraintDelegate() {
ON_CALL(*this, OnConstraintActivated).WillByDefault([this]() {
activated_count++;
});
ON_CALL(*this, OnConstraintBroken).WillByDefault([this]() {
broken_count++;
});
}
~MockPointerConstraintDelegate() = default;
// 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());
int activated_count = 0;
int broken_count = 0;
};
class MockPointerStylusDelegate : public PointerStylusDelegate {
public:
MockPointerStylusDelegate() {}
// Overridden from PointerStylusDelegate:
MOCK_METHOD(void, OnPointerDestroying, (Pointer*));
MOCK_METHOD(void, OnPointerToolChange, (ui::EventPointerType));
MOCK_METHOD(void, OnPointerForce, (base::TimeTicks, float));
MOCK_METHOD(void, OnPointerTilt, (base::TimeTicks, const gfx::Vector2dF&));
};
class TestDataSourceDelegate : public DataSourceDelegate {
public:
TestDataSourceDelegate() {}
TestDataSourceDelegate(const TestDataSourceDelegate&) = delete;
TestDataSourceDelegate& operator=(const TestDataSourceDelegate&) = delete;
// Overridden from DataSourceDelegate:
void OnDataSourceDestroying(DataSource* device) override {}
void OnTarget(const absl::optional<std::string>& mime_type) override {}
void OnSend(const std::string& mime_type, base::ScopedFD fd) override {}
void OnCancelled() override {}
void OnDndDropPerformed() override {}
void OnDndFinished() override {}
void OnAction(DndAction dnd_action) override {}
bool CanAcceptDataEventsForSurface(Surface* surface) const override {
return true;
}
};
class PointerTest : public test::ExoTestBase {
public:
PointerTest() = default;
PointerTest(const PointerTest&) = delete;
PointerTest& operator=(const PointerTest&) = delete;
void SetUp() override {
test::ExoTestBase::SetUp();
// Sometimes underlying infra (i.e. X11 / Xvfb) may emit pointer events
// which can break MockPointerDelegate's expectations, so they should be
// consumed before starting. See https://crbug.com/854674.
base::RunLoop().RunUntilIdle();
}
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
class PointerConstraintTest : public PointerTest {
public:
PointerConstraintTest() = default;
PointerConstraintTest(const PointerConstraintTest&) = delete;
PointerConstraintTest& operator=(const PointerConstraintTest&) = delete;
void SetUp() override {
PointerTest::SetUp();
shell_surface_ = BuildShellSurfaceWhichPermitsPointerLock();
surface_ = shell_surface_->surface_for_testing();
seat_ = std::make_unique<Seat>();
pointer_ = std::make_unique<Pointer>(&delegate_, seat_.get());
focus_client_ =
aura::client::GetFocusClient(ash::Shell::GetPrimaryRootWindow());
focus_client_->FocusWindow(surface_->window());
generator_ = std::make_unique<ui::test::EventGenerator>(
ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate_, CanAcceptPointerEventsForSurface(surface_))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(constraint_delegate_, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface_));
}
void TearDown() override {
// Many objects need to be destroyed before teardown for various reasons.
seat_.reset();
shell_surface_.reset();
surface_ = nullptr;
// Some tests generate mouse events which Pointer::OnMouseEvent() handles
// during the run loop. That routine accesses WMHelper. So, make sure any
// such pending tasks finish before TearDown() destroys the WMHelper.
base::RunLoop().RunUntilIdle();
PointerTest::TearDown();
}
std::unique_ptr<ShellSurface> BuildShellSurfaceWhichPermitsPointerLock() {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
chromeos::kUseOverviewToExitPointerLock, true);
return shell_surface;
}
std::unique_ptr<ui::test::EventGenerator> generator_;
std::unique_ptr<Pointer> pointer_;
std::unique_ptr<Seat> seat_;
testing::NiceMock<MockPointerConstraintDelegate> constraint_delegate_;
testing::NiceMock<MockPointerDelegate> delegate_;
std::unique_ptr<ShellSurface> shell_surface_;
Surface* surface_;
aura::client::FocusClient* focus_client_;
};
#endif
TEST_F(PointerTest, SetCursor) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(1);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
std::unique_ptr<Surface> pointer_surface(new Surface);
std::unique_ptr<Buffer> pointer_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
pointer_surface->Attach(pointer_buffer.get());
pointer_surface->Commit();
// Set pointer surface.
pointer->SetCursor(pointer_surface.get(), gfx::Point(5, 5));
base::RunLoop().RunUntilIdle();
const viz::CompositorRenderPass* last_render_pass;
{
viz::SurfaceId surface_id = pointer->host_window()->GetSurfaceId();
viz::SurfaceManager* surface_manager = GetSurfaceManager();
ASSERT_TRUE(surface_manager->GetSurfaceForId(surface_id)->HasActiveFrame());
const viz::CompositorFrame& frame =
surface_manager->GetSurfaceForId(surface_id)->GetActiveFrame();
EXPECT_EQ(gfx::Rect(0, 0, 10, 10),
frame.render_pass_list.back()->output_rect);
last_render_pass = frame.render_pass_list.back().get();
}
// Adjust hotspot.
pointer->SetCursor(pointer_surface.get(), gfx::Point());
base::RunLoop().RunUntilIdle();
// Verify that adjustment to hotspot resulted in new frame.
{
viz::SurfaceId surface_id = pointer->host_window()->GetSurfaceId();
viz::SurfaceManager* surface_manager = GetSurfaceManager();
ASSERT_TRUE(surface_manager->GetSurfaceForId(surface_id)->HasActiveFrame());
const viz::CompositorFrame& frame =
surface_manager->GetSurfaceForId(surface_id)->GetActiveFrame();
EXPECT_TRUE(frame.render_pass_list.back().get() != last_render_pass);
}
// Unset pointer surface.
pointer->SetCursor(nullptr, gfx::Point());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, SetCursorNull) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(1);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
pointer->SetCursor(nullptr, gfx::Point());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
shell_surface->GetWidget()->GetNativeWindow()->GetRootWindow());
EXPECT_EQ(ui::mojom::CursorType::kNone, cursor_client->GetCursor().type());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, SetCursorType) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(1);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
pointer->SetCursorType(ui::mojom::CursorType::kIBeam);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
shell_surface->GetWidget()->GetNativeWindow()->GetRootWindow());
EXPECT_EQ(ui::mojom::CursorType::kIBeam, cursor_client->GetCursor().type());
// Set the pointer with surface after setting pointer type.
std::unique_ptr<Surface> pointer_surface(new Surface);
std::unique_ptr<Buffer> pointer_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
pointer_surface->Attach(pointer_buffer.get());
pointer_surface->Commit();
pointer->SetCursor(pointer_surface.get(), gfx::Point());
base::RunLoop().RunUntilIdle();
{
viz::SurfaceId surface_id = pointer->host_window()->GetSurfaceId();
viz::SurfaceManager* surface_manager = GetSurfaceManager();
ASSERT_TRUE(surface_manager->GetSurfaceForId(surface_id)->HasActiveFrame());
const viz::CompositorFrame& frame =
surface_manager->GetSurfaceForId(surface_id)->GetActiveFrame();
EXPECT_EQ(gfx::Rect(0, 0, 10, 10),
frame.render_pass_list.back()->output_rect);
}
// Set the pointer type after the pointer surface is specified.
pointer->SetCursorType(ui::mojom::CursorType::kCross);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
EXPECT_EQ(ui::mojom::CursorType::kCross, cursor_client->GetCursor().type());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, SetCursorTypeOutsideOfSurface) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin() -
gfx::Vector2d(1, 1));
pointer->SetCursorType(ui::mojom::CursorType::kIBeam);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
shell_surface->GetWidget()->GetNativeWindow()->GetRootWindow());
// The cursor type shouldn't be the specified one, since the pointer is
// located outside of the surface.
EXPECT_NE(ui::mojom::CursorType::kIBeam, cursor_client->GetCursor().type());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, SetCursorAndSetCursorType) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(1);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
std::unique_ptr<Surface> pointer_surface(new Surface);
std::unique_ptr<Buffer> pointer_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
pointer_surface->Attach(pointer_buffer.get());
pointer_surface->Commit();
// Set pointer surface.
pointer->SetCursor(pointer_surface.get(), gfx::Point());
EXPECT_EQ(1u, pointer->GetActivePresentationCallbacksForTesting().size());
base::RunLoop().RunUntilIdle();
{
viz::SurfaceId surface_id = pointer->host_window()->GetSurfaceId();
viz::SurfaceManager* surface_manager = GetSurfaceManager();
ASSERT_TRUE(surface_manager->GetSurfaceForId(surface_id)->HasActiveFrame());
const viz::CompositorFrame& frame =
surface_manager->GetSurfaceForId(surface_id)->GetActiveFrame();
EXPECT_EQ(gfx::Rect(0, 0, 10, 10),
frame.render_pass_list.back()->output_rect);
}
// Set the cursor type to the kNone through SetCursorType.
pointer->SetCursorType(ui::mojom::CursorType::kNone);
EXPECT_TRUE(pointer->GetActivePresentationCallbacksForTesting().empty());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
// Set the same pointer surface again.
pointer->SetCursor(pointer_surface.get(), gfx::Point());
EXPECT_EQ(1u, pointer->GetActivePresentationCallbacksForTesting().size());
auto& list =
pointer->GetActivePresentationCallbacksForTesting().begin()->second;
base::RunLoop runloop;
list.push_back(base::BindRepeating(
[](base::RepeatingClosure callback, const gfx::PresentationFeedback&) {
callback.Run();
},
runloop.QuitClosure()));
runloop.Run();
{
viz::SurfaceId surface_id = pointer->host_window()->GetSurfaceId();
viz::SurfaceManager* surface_manager = GetSurfaceManager();
ASSERT_TRUE(surface_manager->GetSurfaceForId(surface_id)->HasActiveFrame());
const viz::CompositorFrame& frame =
surface_manager->GetSurfaceForId(surface_id)->GetActiveFrame();
EXPECT_EQ(gfx::Rect(0, 0, 10, 10),
frame.render_pass_list.back()->output_rect);
}
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, SetCursorNullAndSetCursorType) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(1);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
// Set nullptr surface.
pointer->SetCursor(nullptr, gfx::Point());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
shell_surface->GetWidget()->GetNativeWindow()->GetRootWindow());
EXPECT_EQ(ui::mojom::CursorType::kNone, cursor_client->GetCursor().type());
// Set the cursor type.
pointer->SetCursorType(ui::mojom::CursorType::kIBeam);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
EXPECT_EQ(ui::mojom::CursorType::kIBeam, cursor_client->GetCursor().type());
// Set nullptr surface again.
pointer->SetCursor(nullptr, gfx::Point());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, pointer->root_surface());
EXPECT_EQ(ui::mojom::CursorType::kNone, cursor_client->GetCursor().type());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerEnter) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(1);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerLeave) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(4);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerLeave(surface.get()));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().bottom_right());
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerLeave(surface.get()));
shell_surface.reset();
surface.reset();
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerMotion) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(6);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(1, 1));
std::unique_ptr<Surface> sub_surface(new Surface);
std::unique_ptr<SubSurface> sub(
new SubSurface(sub_surface.get(), surface.get()));
surface->SetSubSurfacePosition(sub_surface.get(), gfx::PointF(5, 5));
gfx::Size sub_buffer_size(5, 5);
std::unique_ptr<Buffer> sub_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(sub_buffer_size)));
sub_surface->Attach(sub_buffer.get());
sub_surface->Commit();
surface->Commit();
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(sub_surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerLeave(surface.get()));
EXPECT_CALL(delegate, OnPointerEnter(sub_surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(sub_surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)));
generator.MoveMouseTo(sub_surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(1, 1));
std::unique_ptr<Surface> child_surface(new Surface);
std::unique_ptr<ShellSurface> child_shell_surface(new ShellSurface(
child_surface.get(), gfx::Point(9, 9), /*can_minimize=*/false,
ash::desks_util::GetActiveDeskContainerId()));
child_shell_surface->DisableMovement();
child_shell_surface->SetParent(shell_surface.get());
gfx::Size child_buffer_size(15, 15);
std::unique_ptr<Buffer> child_buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(child_buffer_size)));
child_surface->Attach(child_buffer.get());
child_surface->Commit();
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(child_surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerLeave(sub_surface.get()));
EXPECT_CALL(delegate, OnPointerEnter(child_surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(child_surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(10, 10)));
generator.MoveMouseTo(child_surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(10, 10));
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerButton) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(3);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, true));
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, false));
generator.ClickLeftButton();
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerScroll) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
gfx::Point location = surface->window()->GetBoundsInScreen().origin();
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(3);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(location);
{
// Expect fling stop followed by scroll and scroll stop.
testing::InSequence sequence;
EXPECT_CALL(delegate,
OnPointerScroll(testing::_, gfx::Vector2dF(1.2, 1.2), false));
EXPECT_CALL(delegate, OnPointerScrollStop(testing::_));
}
generator.ScrollSequence(location, base::TimeDelta(), 1, 1, 1, 1);
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerScrollWithThreeFinger) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
gfx::Point location = surface->window()->GetBoundsInScreen().origin();
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(2);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(location);
{
// Expect no scroll.
testing::InSequence sequence;
EXPECT_CALL(delegate, OnPointerScrollStop(testing::_));
}
// Three fingers scroll.
generator.ScrollSequence(location, base::TimeDelta(), 1, 1, 1,
3 /* num_fingers */);
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, OnPointerScrollDiscrete) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(2);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate,
OnPointerScroll(testing::_, gfx::Vector2dF(1, 1), true));
generator.MoveMouseWheel(1, 1);
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, RegisterPointerEventsOnModal) {
// Create modal surface.
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(
new ShellSurface(surface.get(), gfx::Point(), /*can_minimize=*/false,
ash::kShellWindowId_SystemModalContainer));
shell_surface->DisableMovement();
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(5, 5))));
surface->Attach(buffer.get());
surface->Commit();
ash::CenterWindow(shell_surface->GetWidget()->GetNativeWindow());
// Make the window modal.
shell_surface->SetSystemModal(true);
EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, OnPointerFrame()).Times(testing::AnyNumber());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
// Pointer events on modal window should be registered.
gfx::Point origin = surface->window()->GetBoundsInScreen().origin();
{
testing::InSequence sequence;
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(origin);
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)));
generator.MoveMouseTo(origin + gfx::Vector2d(1, 1));
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, true));
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, false));
generator.ClickLeftButton();
EXPECT_CALL(delegate,
OnPointerScroll(testing::_, gfx::Vector2dF(1.2, 1.2), false));
EXPECT_CALL(delegate, OnPointerScrollStop(testing::_));
generator.ScrollSequence(origin, base::TimeDelta(), 1, 1, 1, 1);
}
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, IgnorePointerEventsOnNonModalWhenModalIsOpen) {
// Create surface for non-modal window.
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(10, 10))));
surface->Attach(buffer.get());
surface->Commit();
// Create surface for modal window.
std::unique_ptr<Surface> surface2(new Surface);
std::unique_ptr<ShellSurface> shell_surface2(
new ShellSurface(surface2.get(), gfx::Point(), /*can_minimize=*/false,
ash::kShellWindowId_SystemModalContainer));
shell_surface2->DisableMovement();
std::unique_ptr<Buffer> buffer2(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(5, 5))));
surface2->Attach(buffer2.get());
surface2->Commit();
ash::CenterWindow(shell_surface2->GetWidget()->GetNativeWindow());
// Make the window modal.
shell_surface2->SetSystemModal(true);
EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, OnPointerFrame()).Times(testing::AnyNumber());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface2.get()))
.WillRepeatedly(testing::Return(true));
// Check if pointer events on non-modal window are ignored.
gfx::Point nonModalOrigin = surface->window()->GetBoundsInScreen().origin();
{
testing::InSequence sequence;
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0))
.Times(0);
generator.MoveMouseTo(nonModalOrigin);
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)))
.Times(0);
generator.MoveMouseTo(nonModalOrigin + gfx::Vector2d(1, 1));
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, true))
.Times(0);
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, false))
.Times(0);
generator.ClickLeftButton();
EXPECT_CALL(delegate,
OnPointerScroll(testing::_, gfx::Vector2dF(1.2, 1.2), false))
.Times(0);
EXPECT_CALL(delegate, OnPointerScrollStop(testing::_)).Times(0);
generator.ScrollSequence(nonModalOrigin, base::TimeDelta(), 1, 1, 1, 1);
EXPECT_CALL(delegate, OnPointerLeave(surface.get())).Times(0);
generator.MoveMouseTo(
surface->window()->GetBoundsInScreen().bottom_right());
}
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, IgnorePointerLeaveOnModal) {
// Create modal surface.
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(
new ShellSurface(surface.get(), gfx::Point(), /*can_minimize=*/false,
ash::kShellWindowId_SystemModalContainer));
shell_surface->DisableMovement();
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(5, 5))));
surface->Attach(buffer.get());
surface->Commit();
ash::CenterWindow(shell_surface->GetWidget()->GetNativeWindow());
// Make the window modal.
shell_surface->SetSystemModal(true);
EXPECT_TRUE(ash::Shell::IsSystemModalWindowOpen());
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, OnPointerFrame()).Times(testing::AnyNumber());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
gfx::Point origin = surface->window()->GetBoundsInScreen().origin();
{
testing::InSequence sequence;
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(origin);
// OnPointerLeave should not be called on the modal surface when the pointer
// moves out of its bounds.
EXPECT_CALL(delegate, OnPointerLeave(surface.get())).Times(0);
generator.MoveMouseTo(
surface->window()->GetBoundsInScreen().bottom_right());
}
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, RegisterPointerEventsOnNonModal) {
// Create surface for non-modal window.
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(10, 10))));
surface->Attach(buffer.get());
surface->Commit();
// Create another surface for a non-modal window.
std::unique_ptr<Surface> surface2(new Surface);
std::unique_ptr<ShellSurface> shell_surface2(
new ShellSurface(surface2.get(), gfx::Point(), /*can_minimize=*/false,
ash::kShellWindowId_SystemModalContainer));
shell_surface2->DisableMovement();
std::unique_ptr<Buffer> buffer2(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(5, 5))));
surface2->Attach(buffer2.get());
surface2->Commit();
ash::CenterWindow(shell_surface2->GetWidget()->GetNativeWindow());
MockPointerDelegate delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, OnPointerFrame()).Times(testing::AnyNumber());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface2.get()))
.WillRepeatedly(testing::Return(true));
// Ensure second window is non-modal.
shell_surface2->SetSystemModal(false);
EXPECT_FALSE(ash::Shell::IsSystemModalWindowOpen());
// Check if pointer events on first non-modal window are registered.
gfx::Point firstWindowOrigin =
surface->window()->GetBoundsInScreen().origin();
{
testing::InSequence sequence;
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(firstWindowOrigin);
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)));
generator.MoveMouseTo(firstWindowOrigin + gfx::Vector2d(1, 1));
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, true));
EXPECT_CALL(delegate,
OnPointerButton(testing::_, ui::EF_LEFT_MOUSE_BUTTON, false));
generator.ClickLeftButton();
EXPECT_CALL(delegate,
OnPointerScroll(testing::_, gfx::Vector2dF(1.2, 1.2), false));
EXPECT_CALL(delegate, OnPointerScrollStop(testing::_));
generator.ScrollSequence(firstWindowOrigin, base::TimeDelta(), 1, 1, 1, 1);
EXPECT_CALL(delegate, OnPointerLeave(surface.get()));
generator.MoveMouseTo(
surface->window()->GetBoundsInScreen().bottom_right());
}
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, DragDropAbort) {
Seat seat(std::make_unique<TestDataExchangeDelegate>());
MockPointerDelegate pointer_delegate;
std::unique_ptr<Pointer> pointer(new Pointer(&pointer_delegate, &seat));
TestDataSourceDelegate data_source_delegate;
DataSource source(&data_source_delegate);
Surface origin, icon;
// Make origin into a real window so the pointer can click it
ShellSurface shell_surface(&origin);
Buffer buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(10, 10)));
origin.Attach(&buffer);
origin.Commit();
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(pointer_delegate, CanAcceptPointerEventsForSurface(&origin))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(pointer_delegate, OnPointerFrame()).Times(3);
EXPECT_CALL(pointer_delegate, OnPointerEnter(&origin, gfx::PointF(), 0));
generator.MoveMouseTo(origin.window()->GetBoundsInScreen().origin());
seat.StartDrag(&source, &origin, &icon, ui::mojom::DragEventSource::kMouse);
EXPECT_TRUE(seat.get_drag_drop_operation_for_testing());
EXPECT_CALL(pointer_delegate, OnPointerButton).Times(2);
generator.PressLeftButton();
EXPECT_TRUE(seat.get_drag_drop_operation_for_testing());
generator.ReleaseLeftButton();
EXPECT_FALSE(seat.get_drag_drop_operation_for_testing());
EXPECT_CALL(pointer_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerTest, DragDropAndPointerEnterLeaveEvents) {
Seat seat(std::make_unique<TestDataExchangeDelegate>());
MockPointerDelegate pointer_delegate;
std::unique_ptr<Pointer> pointer(new Pointer(&pointer_delegate, &seat));
TestDataSourceDelegate data_source_delegate;
DataSource source(&data_source_delegate);
Surface origin;
// Make origin into a real window so the pointer can click it
ShellSurface shell_surface(&origin);
Buffer buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(10, 10)));
origin.Attach(&buffer);
origin.Commit();
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(pointer_delegate, CanAcceptPointerEventsForSurface(&origin))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(pointer_delegate, OnPointerFrame()).Times(AnyNumber());
EXPECT_CALL(pointer_delegate, OnPointerEnter(&origin, gfx::PointF(), 0));
generator.MoveMouseTo(origin.window()->GetBoundsInScreen().origin());
auto* drag_drop_controller = static_cast<ash::DragDropController*>(
aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
ASSERT_TRUE(drag_drop_controller);
generator.PressLeftButton();
seat.StartDrag(&source, &origin, /*icon=*/nullptr,
ui::mojom::DragEventSource::kMouse);
EXPECT_TRUE(seat.get_drag_drop_operation_for_testing());
// As soon as the runloop gets triggered, emit a mouse release event.
drag_drop_controller->SetLoopClosureForTesting(
base::BindLambdaForTesting([&]() {
EXPECT_CALL(pointer_delegate, OnPointerEnter(_, _, _));
generator.ReleaseLeftButton();
}),
base::DoNothing());
EXPECT_CALL(pointer_delegate, OnPointerLeave(_));
base::RunLoop().RunUntilIdle();
EXPECT_CALL(pointer_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, DragDropAndPointerEnterLeaveEvents_NoOpOnTouchDrag) {
Seat seat(std::make_unique<TestDataExchangeDelegate>());
MockPointerDelegate pointer_delegate;
std::unique_ptr<Pointer> pointer(new Pointer(&pointer_delegate, &seat));
TestDataSourceDelegate data_source_delegate;
DataSource source(&data_source_delegate);
Surface origin;
// Make origin into a real window so the pointer can click it
ShellSurface shell_surface(&origin);
Buffer buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(10, 10)));
origin.Attach(&buffer);
origin.Commit();
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(pointer_delegate, CanAcceptPointerEventsForSurface(&origin))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(pointer_delegate, OnPointerFrame()).Times(AnyNumber());
EXPECT_CALL(pointer_delegate, OnPointerEnter(&origin, gfx::PointF(), 0));
generator.MoveMouseTo(origin.window()->GetBoundsInScreen().origin());
auto* drag_drop_controller = static_cast<ash::DragDropController*>(
aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
ASSERT_TRUE(drag_drop_controller);
seat.StartDrag(&source, &origin, /*icon=*/nullptr,
ui::mojom::DragEventSource::kTouch);
EXPECT_TRUE(seat.get_drag_drop_operation_for_testing());
// Initiate the gesture sequence.
DispatchGesture(ui::ET_GESTURE_BEGIN, gfx::Point(10, 10));
// As soon as the runloop gets triggered, emit a mouse release event.
drag_drop_controller->SetLoopClosureForTesting(
base::BindLambdaForTesting([&]() {
EXPECT_CALL(pointer_delegate, OnPointerEnter(_, _, _)).Times(0);
// generator.ReleaseLeftButton();
generator.set_current_screen_location(gfx::Point(10, 10));
generator.PressMoveAndReleaseTouchBy(50, 50);
}),
base::DoNothing());
EXPECT_CALL(pointer_delegate, OnPointerLeave(_)).Times(0);
base::RunLoop().RunUntilIdle();
EXPECT_CALL(pointer_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
TEST_F(PointerTest, IgnoresHandledEvents) {
// A very dumb handler that simply marks all events as handled. This is needed
// allows us to mark a mouse event as handled as it gets processed by the
// event processor.
class SetHandledHandler : public ui::EventHandler {
void OnMouseEvent(ui::MouseEvent* event) override { event->SetHandled(); }
};
SetHandledHandler handler;
ash::Shell::Get()->AddPreTargetHandler(&handler);
Seat seat(std::make_unique<TestDataExchangeDelegate>());
testing::NiceMock<MockPointerDelegate> pointer_delegate;
std::unique_ptr<Pointer> pointer(new Pointer(&pointer_delegate, &seat));
// Make origin into a real window so the touch can click it
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
EXPECT_CALL(pointer_delegate, CanAcceptPointerEventsForSurface(testing::_))
.WillRepeatedly(testing::Return(true));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
// The SetHandlerHandler should have marked the event as processed. Therefore
// the event should simply be ignored.
EXPECT_CALL(pointer_delegate,
OnPointerButton(testing::_, testing::_, testing::_))
.Times(0);
// This event should be ignored because it has already been handled.
auto window_point = shell_surface->surface_for_testing()
->window()
->GetBoundsInScreen()
.CenterPoint();
generator.MoveMouseTo(window_point);
generator.ClickLeftButton();
ash::Shell::Get()->RemovePreTargetHandler(&handler);
}
namespace {
class PointerDragDropObserver : public WMHelper::DragDropObserver {
public:
PointerDragDropObserver(DropCallback closure)
: closure_(std::move(closure)) {}
private:
// WMHelper::DragDropObserver overrides:
void OnDragEntered(const ui::DropTargetEvent& event) override {}
aura::client::DragUpdateInfo OnDragUpdated(
const ui::DropTargetEvent& event) override {
return aura::client::DragUpdateInfo();
}
void OnDragExited() override {}
DropCallback GetDropCallback() override { return std::move(closure_); }
DropCallback closure_;
};
} // namespace
// Test for crbug.com/1307143: It ensures no "pointer enter" event is
// processed in case the target surface is destroyed during the drop action.
TEST_F(PointerTest,
DragDropAndPointerEnterLeaveEvents_NoEnterOnSurfaceDestroy) {
Seat seat(std::make_unique<TestDataExchangeDelegate>());
MockPointerDelegate pointer_delegate;
std::unique_ptr<Pointer> pointer(new Pointer(&pointer_delegate, &seat));
TestDataSourceDelegate data_source_delegate;
DataSource source(&data_source_delegate);
std::unique_ptr<Surface> origin(new Surface());
auto* origin_ptr = origin.get();
// Make origin into a real window so the pointer can click it
ShellSurface shell_surface(origin_ptr);
Buffer buffer(exo_test_helper()->CreateGpuMemoryBuffer(gfx::Size(10, 10)));
origin_ptr->Attach(&buffer);
origin_ptr->Commit();
auto closure = base::BindOnce([](std::unique_ptr<Surface> shell_surface,
ui::mojom::DragOperation& output_drag_op) {},
std::move(origin));
PointerDragDropObserver drag_drop_observer(std::move(closure));
auto* wm_helper = WMHelper::GetInstance();
wm_helper->AddDragDropObserver(&drag_drop_observer);
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(pointer_delegate, CanAcceptPointerEventsForSurface(origin_ptr))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(pointer_delegate, OnPointerFrame()).Times(AnyNumber());
EXPECT_CALL(pointer_delegate, OnPointerEnter(origin_ptr, gfx::PointF(), 0));
generator.MoveMouseTo(origin_ptr->window()->GetBoundsInScreen().origin());
auto* drag_drop_controller = static_cast<ash::DragDropController*>(
aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
ASSERT_TRUE(drag_drop_controller);
generator.PressLeftButton();
seat.StartDrag(&source, origin_ptr, /*icon=*/nullptr,
ui::mojom::DragEventSource::kMouse);
EXPECT_TRUE(seat.get_drag_drop_operation_for_testing());
// As soon as the runloop gets triggered, emit a mouse release event.
drag_drop_controller->SetLoopClosureForTesting(
base::BindLambdaForTesting([&]() {
EXPECT_CALL(pointer_delegate, OnPointerEnter(_, _, _));
generator.ReleaseLeftButton();
}),
base::DoNothing());
// OnPointerLeave() gets called twice:
// 1/ when the drag starts;
// 2/ when the dragging window gets destroyed.
EXPECT_CALL(pointer_delegate, OnPointerLeave(_)).Times(2);
base::RunLoop().RunUntilIdle();
wm_helper->RemoveDragDropObserver(&drag_drop_observer);
EXPECT_CALL(pointer_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
// Test for crbug.com/1307143: It ensures no "pointer enter" event is
// processed in case the target surface parent is destroyed during the drop
// action.
TEST_F(PointerTest,
DragDropAndPointerEnterLeaveEvents_NoEnterOnParentSurfaceDestroy) {
Seat seat(std::make_unique<TestDataExchangeDelegate>());
MockPointerDelegate pointer_delegate;
std::unique_ptr<Pointer> pointer(new Pointer(&pointer_delegate, &seat));
TestDataSourceDelegate data_source_delegate;
DataSource source(&data_source_delegate);
auto shell_surface = test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
auto* surface = shell_surface->surface_for_testing();
auto closure = base::BindOnce([](std::unique_ptr<ShellSurface> shell_surface,
ui::mojom::DragOperation& output_drag_op) {},
std::move(shell_surface));
PointerDragDropObserver drag_drop_observer(std::move(closure));
auto* wm_helper = WMHelper::GetInstance();
wm_helper->AddDragDropObserver(&drag_drop_observer);
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(pointer_delegate, CanAcceptPointerEventsForSurface(testing::_))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(pointer_delegate, OnPointerFrame()).Times(AnyNumber());
EXPECT_CALL(pointer_delegate, OnPointerEnter(surface, gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
auto* drag_drop_controller = static_cast<ash::DragDropController*>(
aura::client::GetDragDropClient(ash::Shell::GetPrimaryRootWindow()));
ASSERT_TRUE(drag_drop_controller);
generator.PressLeftButton();
seat.StartDrag(&source, surface, /*icon=*/nullptr,
ui::mojom::DragEventSource::kMouse);
EXPECT_TRUE(seat.get_drag_drop_operation_for_testing());
// As soon as the runloop gets triggered, emit a mouse release event.
drag_drop_controller->SetLoopClosureForTesting(
base::BindLambdaForTesting([&]() {
EXPECT_CALL(pointer_delegate, OnPointerEnter(_, _, _)).Times(1);
generator.ReleaseLeftButton();
}),
base::DoNothing());
// OnPointerLeave() gets called twice:
// 1/ when the drag starts;
// 2/ when the dragging window gets destroyed.
EXPECT_CALL(pointer_delegate, OnPointerLeave(_)).Times(2);
base::RunLoop().RunUntilIdle();
wm_helper->RemoveDragDropObserver(&drag_drop_observer);
EXPECT_CALL(pointer_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
#endif
TEST_F(PointerTest, OnPointerRelativeMotion) {
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
MockRelativePointerDelegate relative_delegate;
Seat seat;
auto pointer = std::make_unique<Pointer>(&delegate, &seat);
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
pointer->RegisterRelativePointerDelegate(&relative_delegate);
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerFrame()).Times(9);
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)));
EXPECT_CALL(
relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(1, 1), testing::_));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(1, 1));
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(2, 2)));
EXPECT_CALL(
relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(1, 1), testing::_));
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(2, 2));
auto sub_surface = std::make_unique<Surface>();
auto sub = std::make_unique<SubSurface>(sub_surface.get(), surface.get());
surface->SetSubSurfacePosition(sub_surface.get(), gfx::PointF(5, 5));
gfx::Size sub_buffer_size(5, 5);
auto sub_buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(sub_buffer_size));
sub_surface->Attach(sub_buffer.get());
sub_surface->Commit();
surface->Commit();
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(sub_surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerLeave(surface.get()));
EXPECT_CALL(delegate, OnPointerEnter(sub_surface.get(), gfx::PointF(), 0));
// OnPointerMotion will not be called, because the pointer location is already
// sent with OnPointerEnter, but we should still receive
// OnPointerRelativeMotion.
EXPECT_CALL(
relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(3, 3), testing::_));
generator.MoveMouseTo(sub_surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(1, 1)));
EXPECT_CALL(
relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(1, 1), testing::_));
generator.MoveMouseTo(sub_surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(1, 1));
const gfx::Point child_surface_origin =
sub_surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(10, 10);
auto child_surface = std::make_unique<Surface>();
auto child_shell_surface = std::make_unique<ShellSurface>(
child_surface.get(), child_surface_origin, /*can_minimize=*/false,
ash::desks_util::GetActiveDeskContainerId());
child_shell_surface->DisableMovement();
child_shell_surface->SetParent(shell_surface.get());
gfx::Size child_buffer_size(15, 15);
auto child_buffer = std::make_unique<Buffer>(
exo_test_helper()->CreateGpuMemoryBuffer(child_buffer_size));
child_surface->Attach(child_buffer.get());
child_surface->Commit();
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(child_surface.get()))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(delegate, OnPointerLeave(sub_surface.get()));
EXPECT_CALL(delegate, OnPointerEnter(child_surface.get(), gfx::PointF(), 0));
// OnPointerMotion will not be called, because the pointer location is already
// sent with OnPointerEnter, but we should still receive
// OnPointerRelativeMotion.
EXPECT_CALL(
relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(9, 9), testing::_));
generator.MoveMouseTo(child_surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerMotion(testing::_, gfx::PointF(10, 10)));
EXPECT_CALL(
relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(10, 10), testing::_));
generator.MoveMouseTo(child_surface->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(10, 10));
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
EXPECT_CALL(relative_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
// TODO(b/161755250): the ifdef is only necessary because of the feature
// flag. This code should work fine on non-cros.
#if BUILDFLAG(IS_CHROMEOS_ASH)
class PointerOrdinalMotionTest : public PointerTest {
public:
PointerOrdinalMotionTest() {
scoped_feature_list_.InitAndEnableFeature(
chromeos::features::kExoOrdinalMotion);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(PointerOrdinalMotionTest, OrdinalMotionOverridesRelativeMotion) {
auto surface = std::make_unique<Surface>();
auto shell_surface = std::make_unique<ShellSurface>(surface.get());
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
// Set up the pointer and move it to the origin.
testing::NiceMock<MockPointerDelegate> delegate;
Seat seat;
auto pointer = std::make_unique<Pointer>(&delegate, &seat);
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
gfx::Point origin = surface->window()->GetBoundsInScreen().origin();
generator.MoveMouseTo(origin);
// Start sending relative motion events.
testing::StrictMock<MockRelativePointerDelegate> relative_delegate;
pointer->RegisterRelativePointerDelegate(&relative_delegate);
// By default, ordinal and relative are the same.
gfx::Point new_location = origin + gfx::Vector2d(1, 1);
ui::MouseEvent ev1(ui::ET_MOUSE_MOVED, new_location, new_location,
ui::EventTimeForNow(), generator.flags(), 0);
EXPECT_CALL(relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(1, 1),
gfx::Vector2dF(1, 1)));
generator.Dispatch(&ev1);
// When set, ordinal overrides the relative motion.
new_location = new_location + gfx::Vector2d(1, 1);
ui::MouseEvent ev2(ui::ET_MOUSE_MOVED, new_location, new_location,
ui::EventTimeForNow(), generator.flags(), 0);
ui::MouseEvent::DispatcherApi(&ev2).set_movement(gfx::Vector2dF(99, 99));
EXPECT_CALL(relative_delegate,
OnPointerRelativeMotion(testing::_, gfx::Vector2dF(1, 1),
gfx::Vector2dF(99, 99)));
generator.Dispatch(&ev2);
pointer->UnregisterRelativePointerDelegate(&relative_delegate);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, ConstrainPointer) {
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate_, OnPointerMotion(testing::_, testing::_)).Times(0);
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin() +
gfx::Vector2d(-1, -1));
auto child_shell_surface = test::ShellSurfaceBuilder({15, 15})
.SetParent(shell_surface_.get())
.SetDisableMovement()
.SetCanMinimize(false)
.BuildShellSurface();
Surface* child_surface = child_shell_surface->surface_for_testing();
EXPECT_CALL(delegate_, CanAcceptPointerEventsForSurface(child_surface))
.WillRepeatedly(testing::Return(true));
generator_->MoveMouseTo(
child_surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate_, OnPointerLeave(surface_));
EXPECT_CALL(delegate_, OnPointerEnter(child_surface, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
// Moving the cursor to a different surface should change the focus when
// the pointer is unconstrained.
pointer_->UnconstrainPointerByUserAction();
generator_->MoveMouseTo(
child_surface->window()->GetBoundsInScreen().origin());
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, CanOnlyConstrainPermittedWindows) {
std::unique_ptr<ShellSurface> shell_surface =
test::ShellSurfaceBuilder({10, 10}).BuildShellSurface();
EXPECT_CALL(constraint_delegate_, GetConstrainedSurface())
.WillRepeatedly(testing::Return(shell_surface->surface_for_testing()));
// Called once when ConstrainPointer is denied, and again when the delegate
// is destroyed.
EXPECT_CALL(constraint_delegate_, OnDefunct()).Times(2);
EXPECT_FALSE(pointer_->ConstrainPointer(&constraint_delegate_));
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, OneConstraintPerSurface) {
ON_CALL(constraint_delegate_, IsPersistent())
.WillByDefault(testing::Return(false));
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame()).Times(testing::AtLeast(1));
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
// Add a second constraint for the same surface, it should fail.
MockPointerConstraintDelegate second_constraint;
EXPECT_CALL(second_constraint, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface_));
ON_CALL(second_constraint, IsPersistent())
.WillByDefault(testing::Return(false));
EXPECT_CALL(second_constraint, OnAlreadyConstrained());
EXPECT_CALL(second_constraint, OnDefunct());
EXPECT_FALSE(pointer_->ConstrainPointer(&second_constraint));
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, OneShotConstraintActivatedOnFirstFocus) {
auto second_shell_surface = BuildShellSurfaceWhichPermitsPointerLock();
Surface* second_surface = second_shell_surface->surface_for_testing();
EXPECT_CALL(delegate_, CanAcceptPointerEventsForSurface(second_surface))
.WillRepeatedly(testing::Return(true));
focus_client_->FocusWindow(second_surface->window());
// Assert: Can no longer activate the constraint on the first surface.
EXPECT_FALSE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_EQ(constraint_delegate_.activated_count, 0);
// Assert: Constraint is activated when first surface gains focus.
focus_client_->FocusWindow(surface_->window());
EXPECT_EQ(constraint_delegate_.activated_count, 1);
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
// Teardown
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, UnconstrainPointerWhenSurfaceIsDestroyed) {
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
// Constraint should be broken if surface is destroyed.
EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
EXPECT_CALL(delegate_, OnPointerLeave(surface_));
EXPECT_CALL(delegate_, OnPointerFrame());
shell_surface_.reset();
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, UnconstrainPointerWhenWindowLosesFocus) {
ON_CALL(constraint_delegate_, IsPersistent())
.WillByDefault(testing::Return(false));
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
EXPECT_CALL(constraint_delegate_, OnConstraintActivated()).Times(0);
focus_client_->FocusWindow(nullptr);
focus_client_->FocusWindow(surface_->window());
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, PersistentConstraintActivatedOnRefocus) {
ON_CALL(constraint_delegate_, IsPersistent())
.WillByDefault(testing::Return(true));
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
focus_client_->FocusWindow(nullptr);
EXPECT_CALL(constraint_delegate_, OnConstraintActivated());
focus_client_->FocusWindow(surface_->window());
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, MultipleSurfacesCanBeConstrained) {
// Arrange: First surface + persistent constraint
ON_CALL(constraint_delegate_, IsPersistent())
.WillByDefault(testing::Return(true));
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame());
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
EXPECT_EQ(constraint_delegate_.activated_count, 1);
// Arrange: Second surface + persistent constraint
auto second_shell_surface = BuildShellSurfaceWhichPermitsPointerLock();
Surface* second_surface = second_shell_surface->surface_for_testing();
focus_client_->FocusWindow(second_surface->window());
EXPECT_CALL(delegate_, CanAcceptPointerEventsForSurface(second_surface))
.WillRepeatedly(testing::Return(true));
testing::NiceMock<MockPointerConstraintDelegate> second_constraint;
EXPECT_CALL(second_constraint, GetConstrainedSurface())
.WillRepeatedly(testing::Return(second_surface));
ON_CALL(second_constraint, IsPersistent())
.WillByDefault(testing::Return(true));
EXPECT_TRUE(pointer_->ConstrainPointer(&second_constraint));
EXPECT_EQ(constraint_delegate_.activated_count, 1);
EXPECT_EQ(second_constraint.activated_count, 1);
// Act: Toggle focus, first surface's constraint should activate.
focus_client_->FocusWindow(surface_->window());
EXPECT_EQ(constraint_delegate_.activated_count, 2);
EXPECT_EQ(second_constraint.activated_count, 1);
// Act: Toggle focus, second surface's constraint should activate.
focus_client_->FocusWindow(second_surface->window());
EXPECT_EQ(constraint_delegate_.activated_count, 2);
EXPECT_EQ(second_constraint.activated_count, 2);
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
pointer_->OnPointerConstraintDelegateDestroying(&second_constraint);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, UserActionPreventsConstraint) {
ON_CALL(constraint_delegate_, IsPersistent())
.WillByDefault(testing::Return(false));
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame()).Times(testing::AtLeast(1));
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
pointer_->UnconstrainPointerByUserAction();
// New constraints are no longer permitted.
MockPointerConstraintDelegate second_constraint;
EXPECT_CALL(second_constraint, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface_));
ON_CALL(second_constraint, IsPersistent())
.WillByDefault(testing::Return(false));
EXPECT_FALSE(pointer_->ConstrainPointer(&second_constraint));
EXPECT_EQ(second_constraint.activated_count, 0);
// A click event will activate the pending constraint.
generator_->ClickLeftButton();
EXPECT_EQ(second_constraint.activated_count, 1);
pointer_->OnPointerConstraintDelegateDestroying(&second_constraint);
// New constraints are now permitted too.
MockPointerConstraintDelegate third_constraint;
EXPECT_CALL(third_constraint, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface_));
ON_CALL(third_constraint, IsPersistent())
.WillByDefault(testing::Return(false));
EXPECT_TRUE(pointer_->ConstrainPointer(&third_constraint));
pointer_->OnPointerConstraintDelegateDestroying(&third_constraint);
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(PointerConstraintTest, UserCanBreakAndActivatePersistentConstraint) {
ON_CALL(constraint_delegate_, IsPersistent())
.WillByDefault(testing::Return(true));
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate_));
EXPECT_EQ(constraint_delegate_.activated_count, 1);
EXPECT_EQ(constraint_delegate_.broken_count, 0);
EXPECT_CALL(delegate_, OnPointerEnter(surface_, gfx::PointF(), 0));
EXPECT_CALL(delegate_, OnPointerFrame()).Times(testing::AtLeast(1));
generator_->MoveMouseTo(surface_->window()->GetBoundsInScreen().origin());
EXPECT_CALL(constraint_delegate_, OnConstraintBroken());
pointer_->UnconstrainPointerByUserAction();
EXPECT_EQ(constraint_delegate_.activated_count, 1);
EXPECT_EQ(constraint_delegate_.broken_count, 1);
// Click events re-enable the constraint.
generator_->ClickLeftButton();
EXPECT_EQ(constraint_delegate_.activated_count, 2);
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate_);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
TEST_F(PointerConstraintTest, DefaultSecrityDeletegate) {
auto default_security_delegate =
SecurityDelegate::GetDefaultSecurityDelegate();
auto shell_surface = test::ShellSurfaceBuilder({10, 10})
.SetSecurityDelegate(default_security_delegate.get())
.BuildShellSurface();
auto* surface = shell_surface->surface_for_testing();
focus_client_->FocusWindow(surface->window());
MockPointerConstraintDelegate constraint_delegate;
EXPECT_CALL(constraint_delegate, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface));
EXPECT_CALL(constraint_delegate, OnDefunct()).Times(1);
EXPECT_FALSE(pointer_->ConstrainPointer(&constraint_delegate));
::testing::Mock::VerifyAndClearExpectations(&constraint_delegate);
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kAppType, static_cast<int>(ash::AppType::LACROS));
EXPECT_CALL(constraint_delegate, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface));
EXPECT_CALL(constraint_delegate, OnDefunct()).Times(0);
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate));
::testing::Mock::VerifyAndClearExpectations(&constraint_delegate);
EXPECT_CALL(constraint_delegate, GetConstrainedSurface())
.WillRepeatedly(testing::Return(surface));
shell_surface->GetWidget()->GetNativeWindow()->SetProperty(
aura::client::kAppType, static_cast<int>(ash::AppType::ARC_APP));
EXPECT_CALL(constraint_delegate, OnDefunct()).Times(0);
EXPECT_TRUE(pointer_->ConstrainPointer(&constraint_delegate));
::testing::Mock::VerifyAndClearExpectations(&constraint_delegate);
pointer_->OnPointerConstraintDelegateDestroying(&constraint_delegate);
EXPECT_CALL(delegate_, OnPointerDestroying(pointer_.get()));
pointer_.reset();
}
#endif
TEST_F(PointerTest, PointerStylus) {
std::unique_ptr<Surface> surface(new Surface);
std::unique_ptr<ShellSurface> shell_surface(new ShellSurface(surface.get()));
gfx::Size buffer_size(10, 10);
std::unique_ptr<Buffer> buffer(
new Buffer(exo_test_helper()->CreateGpuMemoryBuffer(buffer_size)));
surface->Attach(buffer.get());
surface->Commit();
MockPointerDelegate delegate;
MockPointerStylusDelegate stylus_delegate;
Seat seat;
std::unique_ptr<Pointer> pointer(new Pointer(&delegate, &seat));
ui::test::EventGenerator generator(ash::Shell::GetPrimaryRootWindow());
pointer->SetStylusDelegate(&stylus_delegate);
EXPECT_CALL(delegate, CanAcceptPointerEventsForSurface(surface.get()))
.WillRepeatedly(testing::Return(true));
{
testing::InSequence sequence;
EXPECT_CALL(delegate, OnPointerEnter(surface.get(), gfx::PointF(), 0));
EXPECT_CALL(delegate, OnPointerFrame());
EXPECT_CALL(stylus_delegate,
OnPointerToolChange(ui::EventPointerType::kMouse));
EXPECT_CALL(delegate, OnPointerFrame());
}
generator.MoveMouseTo(surface->window()->GetBoundsInScreen().origin());
EXPECT_CALL(delegate, OnPointerDestroying(pointer.get()));
EXPECT_CALL(stylus_delegate, OnPointerDestroying(pointer.get()));
pointer.reset();
}
} // namespace
} // namespace exo