blob: a45afbfb87a128953eda38e3a699912e3897d0c0 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/ozone/platform/x11/x11_window.h"
#include "ui/platform_window/extensions/x11_extension_delegate.h"
#include <memory>
#include <utility>
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display.h"
#include "ui/display/screen_base.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/event.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/events/test/events_test_utils_x11.h"
#include "ui/gfx/x/event.h"
#include "ui/ozone/platform/x11/x11_window_manager.h"
#include "ui/ozone/test/mock_platform_window_delegate.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/platform_window_init_properties.h"
namespace ui {
namespace {
using ::testing::_;
constexpr int kPointerDeviceId = 1;
ACTION_P(StoreWidget, widget_ptr) {
*widget_ptr = arg0;
}
ACTION_P(CloneEvent, event_ptr) {
*event_ptr = arg0->Clone();
}
// TestScreen implementation. We need to set a screen instance, because
// X11Window requires it. And as long as depending on views is a dependency
// violation, keep own implementation here. Otherwise, we could just use
// ScreenOzone, but it is impossible.
// We are not really interested in sending back real displays. Thus, default one
// is more than enough.
class TestScreen : public display::ScreenBase {
public:
TestScreen() {
ProcessDisplayChanged(display::Display(display::kDefaultDisplayId), true);
}
~TestScreen() override = default;
TestScreen(const TestScreen& screen) = delete;
TestScreen& operator=(const TestScreen& screen) = delete;
void SetScaleAndBoundsForPrimaryDisplay(float scale,
const gfx::Rect& bounds_in_pixels) {
auto display = GetPrimaryDisplay();
display.SetScaleAndBounds(scale, bounds_in_pixels);
ProcessDisplayChanged(display, true);
}
};
} // namespace
class X11WindowOzoneTest : public testing::Test {
public:
X11WindowOzoneTest()
: task_env_(std::make_unique<base::test::TaskEnvironment>(
base::test::TaskEnvironment::MainThreadType::UI)) {}
X11WindowOzoneTest(const X11WindowOzoneTest&) = delete;
X11WindowOzoneTest& operator=(const X11WindowOzoneTest&) = delete;
~X11WindowOzoneTest() override = default;
void SetUp() override {
event_source_ = std::make_unique<X11EventSource>(x11::Connection::Get());
display::Screen::SetScreenInstance(&test_screen_);
TouchFactory::GetInstance()->SetPointerDeviceForTest({kPointerDeviceId});
}
void TearDown() override { display::Screen::SetScreenInstance(nullptr); }
protected:
std::unique_ptr<PlatformWindow> CreatePlatformWindow(
MockPlatformWindowDelegate* delegate,
const gfx::Rect& bounds,
gfx::AcceleratedWidget* widget,
X11ExtensionDelegate* x11_extension_delegate) {
EXPECT_CALL(*delegate, OnAcceleratedWidgetAvailable(_))
.WillOnce(StoreWidget(widget));
PlatformWindowInitProperties init_params(bounds);
init_params.x11_extension_delegate = x11_extension_delegate;
auto window = std::make_unique<X11Window>(delegate);
window->Initialize(std::move(init_params));
return std::move(window);
}
void DispatchXEvent(x11::Event* event, gfx::AcceleratedWidget widget) {
auto* device_event = event->As<x11::Input::DeviceEvent>();
DCHECK(device_event);
device_event->event = static_cast<x11::Window>(widget);
x11::Connection::Get()->DispatchEvent(*event);
}
X11WindowManager* window_manager() const {
auto* window_manager = X11WindowManager::GetInstance();
DCHECK(window_manager);
return window_manager;
}
TestScreen test_screen_;
private:
std::unique_ptr<base::test::TaskEnvironment> task_env_;
std::unique_ptr<X11EventSource> event_source_;
};
// This test ensures that events are handled by a right target(widget).
TEST_F(X11WindowOzoneTest, SendPlatformEventToRightTarget) {
MockPlatformWindowDelegate delegate;
gfx::AcceleratedWidget widget;
constexpr gfx::Rect bounds(30, 80, 800, 600);
auto window = CreatePlatformWindow(&delegate, bounds, &widget, nullptr);
ScopedXI2Event xi_event;
xi_event.InitGenericButtonEvent(kPointerDeviceId, EventType::kMousePressed,
gfx::Point(218, 290), EF_NONE);
// First check events can be received by a target window.
std::unique_ptr<Event> event;
EXPECT_CALL(delegate, DispatchEvent(_)).WillOnce(CloneEvent(&event));
DispatchXEvent(xi_event, widget);
EXPECT_EQ(EventType::kMousePressed, event->type());
testing::Mock::VerifyAndClearExpectations(&delegate);
MockPlatformWindowDelegate delegate_2;
gfx::AcceleratedWidget widget_2;
gfx::Rect bounds_2(525, 155, 296, 407);
auto window_2 =
CreatePlatformWindow(&delegate_2, bounds_2, &widget_2, nullptr);
// Check event goes to right target without capture being set.
event.reset();
EXPECT_CALL(delegate, DispatchEvent(_)).Times(0);
EXPECT_CALL(delegate_2, DispatchEvent(_)).WillOnce(CloneEvent(&event));
DispatchXEvent(xi_event, widget_2);
EXPECT_EQ(EventType::kMousePressed, event->type());
EXPECT_CALL(delegate, OnClosed()).Times(1);
EXPECT_CALL(delegate_2, OnClosed()).Times(1);
}
// This test case ensures that events are consumed by a window with explicit
// capture, even though the event is sent to other window.
TEST_F(X11WindowOzoneTest, SendPlatformEventToCapturedWindow) {
MockPlatformWindowDelegate delegate;
gfx::AcceleratedWidget widget;
constexpr gfx::Rect bounds(30, 80, 800, 600);
EXPECT_CALL(delegate, OnClosed()).Times(1);
auto window = CreatePlatformWindow(&delegate, bounds, &widget, nullptr);
MockPlatformWindowDelegate delegate_2;
gfx::AcceleratedWidget widget_2;
gfx::Rect bounds_2(525, 155, 296, 407);
EXPECT_CALL(delegate_2, OnClosed()).Times(1);
auto window_2 =
CreatePlatformWindow(&delegate_2, bounds_2, &widget_2, nullptr);
ScopedXI2Event xi_event;
xi_event.InitGenericButtonEvent(kPointerDeviceId, EventType::kMousePressed,
gfx::Point(218, 290), EF_NONE);
// Set capture to the second window, but send an event to another window
// target. The event must have its location converted and received by the
// captured window instead.
window_2->SetCapture();
std::unique_ptr<Event> event;
EXPECT_CALL(delegate, DispatchEvent(_)).Times(0);
EXPECT_CALL(delegate_2, DispatchEvent(_)).WillOnce(CloneEvent(&event));
DispatchXEvent(xi_event, widget);
EXPECT_TRUE(event.get());
EXPECT_EQ(EventType::kMousePressed, event->type());
EXPECT_EQ(gfx::Point(-277, 215), event->AsLocatedEvent()->location());
}
// This test case ensures window_manager properly provides X11Window instances
// as they are created/destroyed.
TEST_F(X11WindowOzoneTest, GetWindowFromAcceleratedWigets) {
MockPlatformWindowDelegate delegate;
gfx::Rect bounds(0, 0, 100, 100);
gfx::AcceleratedWidget widget_1;
auto window_1 = CreatePlatformWindow(&delegate, bounds, &widget_1, nullptr);
EXPECT_EQ(window_1.get(), window_manager()->GetWindow(widget_1));
gfx::AcceleratedWidget widget_2;
auto window_2 = CreatePlatformWindow(&delegate, bounds, &widget_2, nullptr);
EXPECT_EQ(window_2.get(), window_manager()->GetWindow(widget_2));
EXPECT_EQ(window_1.get(), window_manager()->GetWindow(widget_1));
window_1->Close();
window_1.reset();
EXPECT_EQ(nullptr, window_manager()->GetWindow(widget_1));
EXPECT_EQ(window_2.get(), window_manager()->GetWindow(widget_2));
window_2.reset();
EXPECT_EQ(nullptr, window_manager()->GetWindow(widget_1));
EXPECT_EQ(nullptr, window_manager()->GetWindow(widget_2));
}
// This test case ensures that OnCursorUpdate is called once when a mouse
// location moved to the window, and |window_mouse_currently_on_| is properly
// reset when the window is deleted.
TEST_F(X11WindowOzoneTest, CursorUpdateEnterAndDelete) {
gfx::Rect bounds_1(0, 0, 100, 100);
MockPlatformWindowDelegate delegate_1;
gfx::AcceleratedWidget widget_1;
auto window_1 =
CreatePlatformWindow(&delegate_1, bounds_1, &widget_1, nullptr);
MockPlatformWindowDelegate delegate_2;
gfx::AcceleratedWidget widget_2;
gfx::Rect bounds_2(0, 100, 100, 100);
auto window_2 =
CreatePlatformWindow(&delegate_2, bounds_2, &widget_2, nullptr);
EXPECT_CALL(delegate_1, OnCursorUpdate()).Times(1);
window_manager()->MouseOnWindow(static_cast<X11Window*>(window_1.get()));
// The mouse is already on window_1, and this should not call OnCursorUpdate.
window_manager()->MouseOnWindow(static_cast<X11Window*>(window_1.get()));
EXPECT_CALL(delegate_2, OnCursorUpdate()).Times(1);
window_manager()->MouseOnWindow(static_cast<X11Window*>(window_2.get()));
EXPECT_EQ(window_2.get(),
window_manager()->window_mouse_currently_on_for_test());
// Dispatch Event on window 1 while event is captured on window 2.
::testing::Mock::VerifyAndClearExpectations(&delegate_1);
EXPECT_CALL(delegate_1, OnCursorUpdate()).Times(1);
window_2->SetCapture();
ScopedXI2Event xi_event;
xi_event.InitGenericButtonEvent(kPointerDeviceId, EventType::kMousePressed,
gfx::Point(0, 0), EF_NONE);
DispatchXEvent(xi_event, widget_1);
EXPECT_EQ(window_1.get(),
window_manager()->window_mouse_currently_on_for_test());
// Removing the window should reset the |window_mouse_currently_on_|.
window_1.reset();
EXPECT_FALSE(window_manager()->window_mouse_currently_on_for_test());
}
class FakeX11ExtensionDelegateForSize : public X11ExtensionDelegate {
public:
explicit FakeX11ExtensionDelegateForSize(const gfx::Rect& guessed_size_px)
: guessed_size_px_(guessed_size_px) {}
~FakeX11ExtensionDelegateForSize() override = default;
void OnLostMouseGrab() override {}
#if BUILDFLAG(USE_ATK)
bool OnAtkKeyEvent(AtkKeyEventStruct* atk_key_event,
bool transient) override {
return false;
}
#endif
bool IsOverrideRedirect(const X11Extension& x11_extension) const override {
return false;
}
gfx::Rect GetGuessedFullScreenSizeInPx() const override {
return guessed_size_px_;
}
private:
gfx::Rect guessed_size_px_;
};
// Verifies X11Window sets fullscreen bounds in pixels when going to fullscreen.
TEST_F(X11WindowOzoneTest, SetFullscreen) {
constexpr gfx::Rect screen_bounds_in_px(640, 480, 1280, 720);
test_screen_.SetScaleAndBoundsForPrimaryDisplay(2, screen_bounds_in_px);
MockPlatformWindowDelegate delegate;
gfx::AcceleratedWidget widget;
constexpr gfx::Rect bounds(30, 80, 800, 600);
FakeX11ExtensionDelegateForSize x11_extension_delegate(screen_bounds_in_px);
auto window =
CreatePlatformWindow(&delegate, bounds, &widget, &x11_extension_delegate);
EXPECT_CALL(
delegate,
OnBoundsChanged(testing::Eq(PlatformWindowDelegate::BoundsChange{true})));
window->SetFullscreen(true, display::kInvalidDisplayId);
}
} // namespace ui