blob: 2dba92e5d59edca552f95f9592e044b5a8626c47 [file] [log] [blame]
// Copyright 2016 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/wayland/host/wayland_window.h"
#include <cursor-shape-v1-client-protocol.h>
#include <linux/input.h>
#include <wayland-server-core.h>
#include <xdg-shell-server-protocol.h>
#include <array>
#include <cstddef>
#include <memory>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/nix/xdg_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_command_line.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/hit_test.h"
#include "ui/base/owned_window_anchor.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_types.h"
#include "ui/display/display.h"
#include "ui/display/scoped_display_for_new_windows.h"
#include "ui/display/test/test_screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/gfx/overlay_plane_data.h"
#include "ui/gfx/overlay_priority_hint.h"
#include "ui/gfx/overlay_transform.h"
#include "ui/ozone/common/bitmap_cursor.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
#include "ui/ozone/platform/wayland/common/wayland_util.h"
#include "ui/ozone/platform/wayland/host/wayland_async_cursor.h"
#include "ui/ozone/platform/wayland/host/wayland_buffer_handle.h"
#include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
#include "ui/ozone/platform/wayland/host/wayland_output.h"
#include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
#include "ui/ozone/platform/wayland/host/wayland_seat.h"
#include "ui/ozone/platform/wayland/host/wayland_subsurface.h"
#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom.h"
#include "ui/ozone/platform/wayland/test/mock_pointer.h"
#include "ui/ozone/platform/wayland/test/mock_surface.h"
#include "ui/ozone/platform/wayland/test/mock_wayland_platform_window_delegate.h"
#include "ui/ozone/platform/wayland/test/mock_xdg_surface.h"
#include "ui/ozone/platform/wayland/test/scoped_wl_array.h"
#include "ui/ozone/platform/wayland/test/test_keyboard.h"
#include "ui/ozone/platform/wayland/test/test_output.h"
#include "ui/ozone/platform/wayland/test/test_region.h"
#include "ui/ozone/platform/wayland/test/test_touch.h"
#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h"
#include "ui/ozone/platform/wayland/test/wayland_connection_test_api.h"
#include "ui/ozone/platform/wayland/test/wayland_test.h"
#include "ui/ozone/public/ozone_switches.h"
#include "ui/platform_window/platform_window.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/platform_window/wm/wm_move_resize_handler.h"
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrEq;
using ::testing::Values;
namespace ui {
namespace {
constexpr float kDefaultCursorScale = 1.f;
struct PopupPosition {
gfx::Rect anchor_rect;
gfx::Size size;
uint32_t anchor = 0;
uint32_t gravity = 0;
uint32_t constraint_adjustment = 0;
};
base::ScopedFD MakeFD() {
base::FilePath temp_path;
EXPECT_TRUE(base::CreateTemporaryFile(&temp_path));
auto file =
base::File(temp_path, base::File::FLAG_READ | base::File::FLAG_WRITE |
base::File::FLAG_CREATE_ALWAYS);
return base::ScopedFD(file.TakePlatformFile());
}
// Must happen on the server thread.
wl::TestXdgPopup* GetTestXdgPopupByWindow(wl::TestWaylandServerThread* server,
const uint32_t surface_id) {
DCHECK(server->task_runner()->BelongsToCurrentThread());
wl::MockSurface* mock_surface =
server->GetObject<wl::MockSurface>(surface_id);
if (mock_surface) {
auto* mock_xdg_surface = mock_surface->xdg_surface();
if (mock_xdg_surface) {
return mock_xdg_surface->xdg_popup();
}
}
return nullptr;
}
void AddStateToWlArray(uint32_t state, wl_array* states) {
*static_cast<uint32_t*>(wl_array_add(states, sizeof state)) = state;
}
wl::ScopedWlArray InitializeWlArrayWithActivatedState() {
return wl::ScopedWlArray({XDG_TOPLEVEL_STATE_ACTIVATED});
}
wl::ScopedWlArray MakeStateArray(const std::vector<int32_t> states) {
return wl::ScopedWlArray(states);
}
class MockCursorShape : public WaylandCursorShape {
public:
MockCursorShape() : WaylandCursorShape(nullptr, nullptr) {}
MockCursorShape(const MockCursorShape&) = delete;
MockCursorShape& operator=(const MockCursorShape&) = delete;
~MockCursorShape() override = default;
MOCK_METHOD(void, SetCursorShape, (uint32_t), (override));
};
using BoundsChange = PlatformWindowDelegate::BoundsChange;
constexpr BoundsChange kDefaultBoundsChange{false};
scoped_refptr<PlatformCursor> AsPlatformCursor(
scoped_refptr<BitmapCursor> bitmap_cursor) {
return base::MakeRefCounted<WaylandAsyncCursor>(bitmap_cursor);
}
using DispatchEventCallback = base::OnceCallback<void(Event*)>;
class TestWaylandWindowDelegate : public PlatformWindowDelegate {
public:
void SetDispatchEventCallback(DispatchEventCallback callback) {
callback_ = std::move(callback);
}
// ui::PlatformWindowDelegate implementation.
void OnBoundsChanged(const BoundsChange& change) override {}
void OnDamageRect(const gfx::Rect& damaged_region) override {}
void OnCloseRequest() override {}
void OnClosed() override {}
void OnWindowStateChanged(ui::PlatformWindowState old_state,
ui::PlatformWindowState new_state) override {}
void OnLostCapture() override {}
void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override {}
void OnWillDestroyAcceleratedWidget() override {}
void OnAcceleratedWidgetDestroyed() override {}
void OnActivationChanged(bool active) override {}
void OnCursorUpdate() override {}
void DispatchEvent(Event* event) override { std::move(callback_).Run(event); }
private:
DispatchEventCallback callback_;
};
} // namespace
class WaylandWindowTest : public WaylandTest {
public:
WaylandWindowTest()
: test_mouse_event_(EventType::kMousePressed,
gfx::Point(10, 15),
gfx::Point(10, 15),
ui::EventTimeStampFromSeconds(123456),
EF_LEFT_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON,
EF_LEFT_MOUSE_BUTTON) {}
WaylandWindowTest(const WaylandWindowTest&) = delete;
WaylandWindowTest& operator=(const WaylandWindowTest&) = delete;
void SetUp() override {
WaylandTest::SetUp();
buffer_id_gen_ = 0u;
frame_id_gen_ = 0u;
surface_id_ = window_->root_surface()->get_surface_id();
PostToServerAndWait(
[id = surface_id_](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface->xdg_surface());
});
}
protected:
void SendConfigureEventPopup(WaylandWindow* menu_window,
const gfx::Rect& bounds) {
const uint32_t surface_id = menu_window->root_surface()->get_surface_id();
PostToServerAndWait(
[surface_id, bounds](wl::TestWaylandServerThread* server) {
auto* popup = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(popup);
xdg_popup_send_configure(popup->resource(), bounds.x(), bounds.y(),
bounds.width(), bounds.height());
});
}
// Simulates up to date buffers coming through viz and being latched.
// Call this after configures or anything where you want wayland or latched
// state to update.
void AdvanceFrameToCurrent(
WaylandWindow* window,
const MockWaylandPlatformWindowDelegate& delegate) {
AdvanceFrameToGivenVizSequenceId(window, delegate, delegate.viz_seq());
}
void AdvanceFrameToGivenVizSequenceId(
WaylandWindow* window,
const MockWaylandPlatformWindowDelegate& delegate,
int64_t viz_seq) {
WaylandTestBase::SyncDisplay();
window->OnSequencePoint(viz_seq);
window->root_surface()->ApplyPendingState();
}
void InitializeWithSupportedHitTestValues(std::vector<int>* hit_tests) {
hit_tests->push_back(static_cast<int>(HTBOTTOM));
hit_tests->push_back(static_cast<int>(HTBOTTOMLEFT));
hit_tests->push_back(static_cast<int>(HTBOTTOMRIGHT));
hit_tests->push_back(static_cast<int>(HTLEFT));
hit_tests->push_back(static_cast<int>(HTRIGHT));
hit_tests->push_back(static_cast<int>(HTTOP));
hit_tests->push_back(static_cast<int>(HTTOPLEFT));
hit_tests->push_back(static_cast<int>(HTTOPRIGHT));
}
MockCursorShape* InstallMockCursorShape() {
auto mock_cursor_shapes = std::make_unique<MockCursorShape>();
MockCursorShape* mock_cursor_shapes_ptr = mock_cursor_shapes.get();
WaylandConnectionTestApi test_api(connection_.get());
test_api.SetCursorShape(std::move(mock_cursor_shapes));
return mock_cursor_shapes_ptr;
}
// Verifies and clearis expectations for a toplevel window associated with
// `delegate` and whose root surface id is `surface_id`. Both client and
// server-side expectations are checked, including xdg-toplevel as well as
// wp-viewport.
void VerifyAndClearExpectations(MockWaylandPlatformWindowDelegate& delegate,
uint32_t surface_id) {
// Client side verification.
Mock::VerifyAndClearExpectations(&delegate);
// Server side verification.
// `PostToServerAndWait` runs `RoundTripQueue` to wait for the queue to be
// empty. It makes sure that `VerifyAndClearExpectations` below will run
// after all requests had been handled.
PostToServerAndWait([id = surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
// Verify the expectations for mock objects.
Mock::VerifyAndClearExpectations(mock_surface);
Mock::VerifyAndClearExpectations(xdg_surface);
Mock::VerifyAndClearExpectations(xdg_surface->xdg_toplevel());
Mock::VerifyAndClearExpectations(mock_surface->viewport());
});
}
// Verifies expectations for `window_` on both client and server. It does not
// handle all expectations, so more calls to Mock::VerifyAndClearExpectations
// for other associated mock objects not listed in this method might be
// needed.
//
// Note: It is not required to call this at the end of the test to verify the
// expectations.
void VerifyAndClearExpectations() {
VerifyAndClearExpectations(delegate_, surface_id_);
}
void VerifyXdgPopupPosition(WaylandWindow* menu_window,
const PopupPosition& position) {
const uint32_t surface_id = menu_window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id,
position](wl::TestWaylandServerThread* server) {
auto* popup = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(popup);
EXPECT_EQ(popup->anchor_rect(), position.anchor_rect);
EXPECT_EQ(popup->size(), position.size);
EXPECT_EQ(popup->anchor(), position.anchor);
EXPECT_EQ(popup->gravity(), position.gravity);
EXPECT_EQ(popup->constraint_adjustment(), position.constraint_adjustment);
});
}
void VerifyCanDispatchMouseEvents(
WaylandWindow* dispatching_window,
const std::vector<WaylandWindow*>& non_dispatching_windows) {
auto* pointer_focused_window =
connection_->window_manager()->GetCurrentPointerFocusedWindow();
ASSERT_TRUE(pointer_focused_window);
Event::DispatcherApi(&test_mouse_event_).set_target(pointer_focused_window);
EXPECT_TRUE(dispatching_window->CanDispatchEvent(&test_mouse_event_));
for (auto* window : non_dispatching_windows) {
EXPECT_FALSE(window->CanDispatchEvent(&test_mouse_event_));
}
}
void VerifyCanDispatchTouchEvents(
const std::vector<WaylandWindow*>& dispatching_windows,
const std::vector<WaylandWindow*>& non_dispatching_windows) {
ASSERT_LT(dispatching_windows.size(), 2u);
auto* touch_focused_window =
connection_->window_manager()->GetCurrentTouchFocusedWindow();
// There must be focused window to dispatch.
if (dispatching_windows.size() == 0) {
EXPECT_FALSE(touch_focused_window);
}
PointerDetails pointer_details(EventPointerType::kTouch, 1);
TouchEvent test_touch_event(EventType::kTouchPressed, {1, 1},
base::TimeTicks(), pointer_details);
if (touch_focused_window) {
Event::DispatcherApi(&test_touch_event).set_target(touch_focused_window);
}
for (auto* window : dispatching_windows) {
EXPECT_TRUE(window->CanDispatchEvent(&test_touch_event));
}
for (auto* window : non_dispatching_windows) {
// Make sure that the CanDispatcEvent works on release build.
#if DCHECK_IS_ON()
// Disable DCHECK when enabled.
window->disable_null_target_dcheck_for_testing();
#endif
EXPECT_FALSE(window->CanDispatchEvent(&test_touch_event));
}
}
void VerifyCanDispatchKeyEvents(
const std::vector<WaylandWindow*>& dispatching_windows,
const std::vector<WaylandWindow*>& non_dispatching_windows) {
ASSERT_LT(dispatching_windows.size(), 2u);
auto* keyboard_focused_window =
connection_->window_manager()->GetCurrentKeyboardFocusedWindow();
// There must be focused window to dispatch.
if (dispatching_windows.size() == 0) {
EXPECT_FALSE(keyboard_focused_window);
}
KeyEvent test_key_event(EventType::kKeyPressed, VKEY_0, 0);
if (keyboard_focused_window) {
Event::DispatcherApi(&test_key_event).set_target(keyboard_focused_window);
}
for (auto* window : dispatching_windows) {
EXPECT_TRUE(window->CanDispatchEvent(&test_key_event));
}
for (auto* window : non_dispatching_windows) {
// Make sure that the CanDispatcEvent works on release build.
#if DCHECK_IS_ON()
// Disable DCHECK when enabled.
window->disable_null_target_dcheck_for_testing();
#endif
EXPECT_FALSE(window->CanDispatchEvent(&test_key_event));
}
}
uint32_t GetObjIdForOutput(WaylandOutput::Id id) {
auto* output_manager = connection_->wayland_output_manager();
auto* wayland_output = output_manager->GetOutput(id);
if (wayland_output) {
return wl_proxy_get_id(
reinterpret_cast<wl_proxy*>(wayland_output->get_output()));
}
return 0;
}
WaylandBufferHandle* CreateTestShmBuffer(const gfx::Size& buffer_size,
WaylandSurface* surface) {
CHECK(!buffer_size.IsEmpty());
CHECK(surface);
CHECK(connection_->buffer_manager_host());
auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
buffer_manager_gpu_->Initialize(std::move(interface_ptr), {},
/*supports_dma_buf=*/false,
/*supports_viewporter=*/true,
/*supports_acquire_fence=*/false,
/*supports_overlays=*/true,
/*supports_single_pixel_buffer=*/true);
const uint32_t buffer_id = ++buffer_id_gen_;
auto length = buffer_size.width() * buffer_size.height() * 4;
buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
buffer_id);
task_environment_.RunUntilIdle();
return connection_->buffer_manager_host()->EnsureBufferHandle(surface,
buffer_id);
}
// Emulates a new frame being received from Viz being processed by `window`s
// frame manager.
void CreateBufferAndPresentAsNewFrame(
WaylandWindow* window,
const MockWaylandPlatformWindowDelegate& delegate,
const gfx::Size& buffer_size,
float buffer_scale) {
CHECK(window);
CHECK(window->root_surface());
auto* buffer = CreateTestShmBuffer(buffer_size, window->root_surface());
ASSERT_TRUE(buffer);
window->root_surface()->AttachBuffer(buffer);
const uint32_t frame_id = ++frame_id_gen_;
wl::WaylandOverlayConfig root_config;
root_config.buffer_id = buffer->id();
root_config.bounds_rect = gfx::RectF(buffer_size);
root_config.damage_region = gfx::Rect(buffer_size);
root_config.surface_scale_factor = buffer_scale;
std::vector<wl::WaylandOverlayConfig> configs;
configs.push_back(std::move(root_config));
window->CommitOverlays(frame_id, gfx::FrameData(delegate.viz_seq()),
configs);
}
// Surface id of |window|'s the root surface. Stored for convenience.
uint32_t surface_id_ = 0u;
MouseEvent test_mouse_event_;
// Incremental id used to generate buffer IDs.
uint32_t buffer_id_gen_ = 0u;
// Incremental id used to generate frame IDs.
uint32_t frame_id_gen_ = 0u;
};
// Regression test for crbug.com/1433175
TEST_P(WaylandWindowTest, Shutdown) {
window_->PrepareForShutdown();
window_->OnDragSessionClose(mojom::DragOperation::kNone);
}
TEST_P(WaylandWindowTest, SetTitle) {
window_->SetTitle(u"hello");
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
EXPECT_EQ("hello", surface->xdg_surface()->xdg_toplevel()->title());
});
}
TEST_P(WaylandWindowTest, OnSequencePointConfiguresWaylandWindow) {
constexpr gfx::Rect kNormalBounds{500, 300};
// Configure event makes Wayland update bounds, but does not change toplevel
// input region, opaque region or window geometry immediately. Such actions
// are postponed to OnSequencePoint();
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
});
auto state = InitializeWlArrayWithActivatedState();
constexpr uint32_t kConfigureSerial = 2u;
SendConfigureEvent(surface_id_, kNormalBounds.size(), state,
kConfigureSerial);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial));
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
EXPECT_CALL(*mock_surface, SetInputRegion(_));
});
AdvanceFrameToCurrent(window_.get(), delegate_);
}
// WaylandSurface state changes are sent to wayland compositor when
// ApplyPendingState() is called.
TEST_P(WaylandWindowTest, ApplyPendingStatesAndCommit) {
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
// Set*() calls do not send wl_surface requests.
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
EXPECT_CALL(*mock_surface, SetBufferScale(2)).Times(0);
});
std::vector<gfx::Rect> region_px = {gfx::Rect{500, 300}};
window_->root_surface()->set_opaque_region(region_px);
window_->root_surface()->set_input_region(region_px);
window_->root_surface()->set_surface_buffer_scale(2);
VerifyAndClearExpectations();
WaylandTestBase::SyncDisplay();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
// ApplyPendingState() generates wl_surface requests and Commit() causes a
// wayland connection flush.
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
EXPECT_CALL(*mock_surface, SetBufferScale(2)).Times(1);
EXPECT_CALL(*mock_surface, Commit()).Times(1);
});
window_->root_surface()->ApplyPendingState();
window_->root_surface()->Commit();
VerifyAndClearExpectations();
WaylandTestBase::SyncDisplay();
}
// Checks that when the window gets some of its edges tiled, it notifies the
// delegate appropriately.
TEST_P(WaylandWindowTest, HandleTiledEdges) {
constexpr gfx::Rect kWindowBounds{800, 600};
struct {
std::vector<xdg_toplevel_state> configured_states;
WindowTiledEdges expected_tiled_edges;
} kTestCases[]{
{{XDG_TOPLEVEL_STATE_TILED_LEFT}, {true, false, false, false}},
{{XDG_TOPLEVEL_STATE_TILED_RIGHT}, {false, true, false, false}},
{{XDG_TOPLEVEL_STATE_TILED_TOP}, {false, false, true, false}},
{{XDG_TOPLEVEL_STATE_TILED_BOTTOM}, {false, false, false, true}},
{{XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_TOP},
{true, false, true, false}},
{{XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_BOTTOM},
{true, false, false, true}},
{{XDG_TOPLEVEL_STATE_TILED_RIGHT, XDG_TOPLEVEL_STATE_TILED_TOP},
{false, true, true, false}},
{{XDG_TOPLEVEL_STATE_TILED_RIGHT, XDG_TOPLEVEL_STATE_TILED_BOTTOM},
{false, true, false, true}},
{{XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_TOP,
XDG_TOPLEVEL_STATE_TILED_BOTTOM},
{true, false, true, true}},
{{XDG_TOPLEVEL_STATE_TILED_RIGHT, XDG_TOPLEVEL_STATE_TILED_TOP,
XDG_TOPLEVEL_STATE_TILED_BOTTOM},
{false, true, true, true}},
};
for (const auto& test_case : kTestCases) {
auto configured_states = InitializeWlArrayWithActivatedState();
for (const auto additional_state : test_case.configured_states) {
AddStateToWlArray(additional_state, configured_states.get());
}
EXPECT_CALL(delegate_,
OnWindowTiledStateChanged(test_case.expected_tiled_edges))
.Times(1);
SendConfigureEvent(surface_id_, kWindowBounds.size(), configured_states);
VerifyAndClearExpectations();
}
}
TEST_P(WaylandWindowTest, DisregardUnpassedWindowConfigure) {
constexpr gfx::Rect kNormalBounds1{500, 300};
constexpr gfx::Rect kNormalBounds2{800, 600};
constexpr gfx::Rect kNormalBounds3{700, 400};
uint32_t serial = 1;
// Send 3 configures, and skip OnSequencePoint for the result of the second
// configure. The second configure should not be acked or have its properties
// applied.
PostToServerAndWait(
[id = surface_id_, bounds1 = kNormalBounds1, bounds2 = kNormalBounds2,
bounds3 = kNormalBounds3](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds1.size())));
EXPECT_CALL(*xdg_surface, AckConfigure(2));
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds2.size())))
.Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(3)).Times(0);
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds3.size())));
EXPECT_CALL(*xdg_surface, AckConfigure(4));
});
auto state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds1.size(), state, ++serial);
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds2.size(), state, ++serial);
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds3.size(), state, ++serial);
window_->OnSequencePoint(/*seq=*/1);
window_->OnSequencePoint(/*seq=*/3);
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, MismatchedSequencePoints) {
constexpr gfx::Rect kNormalBounds1{500, 300};
constexpr gfx::Rect kNormalBounds2{800, 600};
constexpr gfx::Rect kNormalBounds3{700, 400};
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
// OnSequencePoint with mismatched sequence points from configure
// events does not acknowledge toplevel configure.
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
});
auto state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds1.size(), state);
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds2.size(), state);
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds3.size(), state);
// Needs sequence point > 0 to latch.
window_->OnSequencePoint(0);
VerifyAndClearExpectations();
}
// Regression test for bugs like crbug.com/413007181 and crbug.com/340363673.
TEST_P(WaylandWindowTest, GeometrySentOnTilingStateChange) {
constexpr gfx::Rect kBounds{800, 600};
// Make sure the window has only active state initially.
auto active = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED});
SendConfigureEvent(surface_id_, {0, 0}, active);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Now add tiled state and check that the geometry is sent.
auto active_tiled = MakeStateArray(
{XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_TILED_LEFT});
PostToServerAndWait([id = surface_id_,
bounds = kBounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
EXPECT_CALL(*xdg_surface, AckConfigure(_));
});
SendConfigureEvent(surface_id_, kBounds.size(), active_tiled);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Now remove tiled state and check that the geometry is sent.
PostToServerAndWait([id = surface_id_,
bounds = kBounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
EXPECT_CALL(*xdg_surface, AckConfigure(_));
});
SendConfigureEvent(surface_id_, kBounds.size(), active);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, OnSequencePointClearsPreviousUnackedConfigures) {
constexpr gfx::Rect kNormalBounds1{500, 300};
constexpr gfx::Rect kNormalBounds2{800, 600};
constexpr gfx::Rect kNormalBounds3{700, 400};
uint32_t serial = 1;
auto state = InitializeWlArrayWithActivatedState();
// Send 3 configures. Waiting to advance the frame (and call
// OnSequencePoint(3)) should mean acking and processing the completion of
// first two configures will be skipped.
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds1](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(2)).Times(0);
});
SendConfigureEvent(surface_id_, kNormalBounds1.size(), state, ++serial);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds2](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(3)).Times(0);
});
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds2.size(), state, ++serial);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds3](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
EXPECT_CALL(*xdg_surface, AckConfigure(4));
});
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds3.size(), state, ++serial);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
}
// This test used to specifically to guard against origin being set to (0, 0)
// thus lacros could be restored to correct display (crbug.com/1423690).
// TODO(crbug.com/374244479) Given lacros has been deprecated, update this test
// to ignore the origin and only verify the size as origin is not known in
// Wayland world.
TEST_P(WaylandWindowTest, RestoredBoundsSetWithCorrectOrigin) {
constexpr gfx::Rect kNormalBounds{1376, 10, 500, 300};
constexpr gfx::Rect kMaximizedBounds{1366, 0, 800, 600};
// Make sure the window has normal state initially.
window_->SetBoundsInDIP(kNormalBounds);
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Deactivate the surface.
auto empty_state = MakeStateArray({});
SendConfigureEvent(surface_id_, {0, 0}, empty_state);
AdvanceFrameToCurrent(window_.get(), delegate_);
WaylandWindow::WindowStates window_states;
window_states.is_maximized = true;
window_states.is_activated = true;
window_->HandleToplevelConfigure(kMaximizedBounds.width(),
kMaximizedBounds.height(), window_states);
window_->HandleSurfaceConfigure(2);
EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
EXPECT_EQ(window_->GetRestoredBoundsInDIP(), kNormalBounds);
}
TEST_P(WaylandWindowTest, MaximizeAndRestore) {
constexpr gfx::Rect kNormalBounds{500, 300};
constexpr gfx::Rect kMaximizedBounds{800, 600};
// Make sure the window has normal state initially.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Deactivate the surface.
auto empty_state = MakeStateArray({});
SendConfigureEvent(surface_id_, {0, 0}, empty_state);
AdvanceFrameToCurrent(window_.get(), delegate_);
auto active_maximized = MakeStateArray(
{XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
PostToServerAndWait([id = surface_id_, bounds = kMaximizedBounds](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
// Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
// the real delegate. Its OnWindowStateChanged() may (through some chain of
// calls) invoke SetWindowGeometry(), but that should not happen during the
// change of the window state.
// See https://crbug.com/1223005.
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Maximize();
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED});
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), inactive_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
});
// Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
// the real delegate. Its OnWindowStateChanged() may (through some chain of
// calls) invoke SetWindowGeometry(), but that should not happen during the
// change of the window state.
// See https://crbug.com/1223005.
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetMaximized());
});
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
auto active = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, active);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, MaximizeAndRestoreWithInsets) {
constexpr gfx::Rect kNormalBounds{510, 310};
constexpr gfx::Insets kNormalInsets(5);
constexpr gfx::Rect kMaximizedBounds{800, 600};
constexpr gfx::Insets kMaximizedInsets(0);
// Make sure the window has normal state initially.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Deactivate the surface.
auto empty_state = MakeStateArray({});
SendConfigureEvent(surface_id_, {0, 0}, empty_state);
AdvanceFrameToCurrent(window_.get(), delegate_);
auto maximized_geometry = kMaximizedBounds;
maximized_geometry.Inset(kMaximizedInsets);
auto active_maximized = MakeStateArray(
{XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
PostToServerAndWait([id = surface_id_, bounds = maximized_geometry](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
// Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
// the real delegate. Its OnWindowStateChanged() may (through some chain of
// calls) invoke SetWindowGeometry(), but that should not happen during the
// change of the window state.
// See https://crbug.com/1223005.
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
.WillRepeatedly(Return(kMaximizedInsets));
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Maximize();
SendConfigureEvent(surface_id_, maximized_geometry.size(), active_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED});
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
.WillRepeatedly(Return(kMaximizedInsets));
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
SendConfigureEvent(surface_id_, maximized_geometry.size(),
inactive_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
.WillRepeatedly(Return(kMaximizedInsets));
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
SendConfigureEvent(surface_id_, maximized_geometry.size(), active_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
auto normal_geometry = kNormalBounds;
normal_geometry.Inset(kNormalInsets);
// Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
// the real delegate. Its OnWindowStateChanged() may (through some chain of
// calls) invoke SetWindowGeometry(), but that should not happen during the
// change of the window state.
// See https://crbug.com/1223005.
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
.WillRepeatedly(Return(kNormalInsets));
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
PostToServerAndWait([id = surface_id_, bounds = normal_geometry](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetMaximized());
EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
});
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
auto active = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, active);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, Minimize) {
wl::ScopedWlArray states({});
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
SendConfigureEvent(surface_id_, {0, 0}, states);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMinimized());
});
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Minimize();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
VerifyAndClearExpectations();
// Reinitialize wl_array, which removes previous old states.
states = wl::ScopedWlArray({});
SendConfigureEvent(surface_id_, {0, 0}, states);
// Wayland compositor doesn't notify clients about minimized state, but rather
// if a window is not activated. Thus, a WaylandToplevelWindow marks itself as
// being minimized and and sets state to minimized. Thus, the state mustn't
// change after the configuration event is sent.
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
// Send one additional empty configuration event (which means the surface is
// not maximized, fullscreen or activated) to ensure, WaylandWindow stays in
// the same minimized state, but the delegate is always notified.
//
// TODO(tonikito): Improve filtering of delegate notification here.
ui::PlatformWindowState state;
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _))
.WillRepeatedly(DoAll(SaveArg<0>(&state), InvokeWithoutArgs([&]() {
EXPECT_EQ(state, PlatformWindowState::kMinimized);
})));
SendConfigureEvent(surface_id_, {0, 0}, states);
// And one last time to ensure the behaviour.
SendConfigureEvent(surface_id_, {0, 0}, states);
}
// Tests the event sequence where a toplevel window is minimized and a restore
// event is initiated by the server.
TEST_P(WaylandWindowTest, ServerInitiatedRestoreFromMinimizedState) {
wl::ScopedWlArray states({});
// Make sure the window is initialized to the normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
SendConfigureEvent(surface_id_, {0, 0}, states);
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
// A subsequent minimize event from the server should notify delegates of the
// window state change.
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
{
WaylandWindow::WindowStates window_states;
window_states.is_minimized = true;
window_->HandleToplevelConfigureWithOrigin(0, 0, 0, 0, window_states);
}
window_->HandleSurfaceConfigure(3);
EXPECT_EQ(PlatformWindowState::kMinimized, window_->GetPlatformWindowState());
// If the minimized state is not supported, the server initiated restore
// event with no window activation should not restore the window. It should
// instead leave the window in the minimized state.
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
window_->HandleToplevelConfigureWithOrigin(0, 0, 0, 0, {});
window_->HandleSurfaceConfigure(4);
EXPECT_EQ(PlatformWindowState::kMinimized, window_->GetPlatformWindowState());
}
TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
constexpr gfx::Rect kNormalBounds{500, 300};
constexpr gfx::Rect kFullscreenBounds{800, 600};
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetFullscreen());
});
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->SetFullscreen(true, display::kInvalidDisplayId);
// Make sure than WaylandWindow manually handles fullscreen states. Check the
// comment in the WaylandWindow::SetFullscreen.
VerifyAndClearExpectations();
SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
EXPECT_EQ(window_->GetPlatformWindowState(),
PlatformWindowState::kFullScreen);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetFullscreen());
});
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Restore();
VerifyAndClearExpectations();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
}
TEST_P(WaylandWindowTest, StartWithFullscreen) {
MockWaylandPlatformWindowDelegate delegate(connection_.get());
PlatformWindowInitProperties properties;
properties.bounds = gfx::Rect(100, 100);
properties.type = PlatformWindowType::kWindow;
// We need to create a window avoid calling Show() on it as it is what upper
// views layer does - when Widget initialize DesktopWindowTreeHost, the Show()
// is called later down the road, but Maximize may be called earlier. We
// cannot process them and set a pending state instead, because ShellSurface
// is not created by that moment.
auto window =
delegate.CreateWaylandWindow(connection_.get(), std::move(properties));
WaylandTestBase::SyncDisplay();
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window->GetPlatformWindowState());
// The state must not be changed to the fullscreen before the surface is
// activated.
const uint32_t surface_id = window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface =
server->GetObject<wl::MockSurface>(surface_id);
EXPECT_FALSE(mock_surface->xdg_surface());
});
// We must receive a state change after SetFullscreen.
EXPECT_CALL(delegate,
OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
Eq(PlatformWindowState::kFullScreen)))
.Times(1);
window->SetFullscreen(true, display::kInvalidDisplayId);
// The state of the window must already be fullscreen one.
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
WaylandTestBase::SyncDisplay();
Mock::VerifyAndClearExpectations(&delegate);
// Show and Activate the surface.
window->Show(false);
// We mustn't receive any state changes if that does not differ from the last
// state.
EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
SendConfigureEvent(surface_id, {0, 0}, states);
// It must be still the same state.
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
Mock::VerifyAndClearExpectations(&delegate);
}
TEST_P(WaylandWindowTest, StartMaximized) {
MockWaylandPlatformWindowDelegate delegate(connection_.get());
PlatformWindowInitProperties properties;
properties.bounds = gfx::Rect(100, 100);
properties.type = PlatformWindowType::kWindow;
// We need to create a window avoid calling Show() on it as it is what upper
// views layer does - when Widget initialize DesktopWindowTreeHost, the Show()
// is called later down the road, but Maximize may be called earlier. We
// cannot process them and set a pending state instead, because ShellSurface
// is not created by that moment.
auto window =
delegate.CreateWaylandWindow(connection_.get(), std::move(properties));
WaylandTestBase::SyncDisplay();
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window->GetPlatformWindowState());
// The state gets changed to maximize and the delegate notified.
const uint32_t surface_id = window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface =
server->GetObject<wl::MockSurface>(surface_id);
EXPECT_FALSE(mock_surface->xdg_surface());
});
// We must receive a state change after Show is called.
EXPECT_CALL(delegate,
OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
Eq(PlatformWindowState::kMaximized)))
.Times(1);
window->Maximize();
// The state of the window must already be fullscreen one.
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kMaximized);
WaylandTestBase::SyncDisplay();
Mock::VerifyAndClearExpectations(&delegate);
// Show the window now.
window->Show(false);
// Window show state should be already up to date, so delegate is not
// notified.
EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kMaximized);
// Activate the surface.
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
SendConfigureEvent(surface_id, {0, 0}, states);
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kMaximized);
Mock::VerifyAndClearExpectations(&delegate);
}
TEST_P(WaylandWindowTest, CompositorSideStateChanges) {
// Real insets used by default on HiDPI.
const auto kInsets = gfx::Insets::TLBR(38, 44, 55, 44);
const auto kNormalBounds = window_->GetBoundsInDIP();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
// Set nonzero insets and ensure that they are only used when the window has
// normal state.
// See https://crbug.com/1274629
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
.WillRepeatedly(Return(gfx::Insets()));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
.Times(1);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect{2000, 2000}));
});
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
SendConfigureEvent(surface_id_, {2000, 2000}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
VerifyAndClearExpectations();
// Unmaximize
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
.WillRepeatedly(Return(kInsets));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
.Times(1);
PostToServerAndWait(
[id = surface_id_, insets = kInsets,
bounds = kNormalBounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface,
SetWindowGeometry(gfx::Rect(
insets.left(), insets.top(),
bounds.width() - (insets.left() + insets.right()),
bounds.height() - (insets.top() + insets.bottom()))));
});
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Now, set to fullscreen.
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kFullScreen))
.WillRepeatedly(Return(gfx::Insets()));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
.Times(1);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(2005, 2005)));
});
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
SendConfigureEvent(surface_id_, {2005, 2005}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Unfullscreen
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
.WillRepeatedly(Return(kInsets));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
.Times(1);
PostToServerAndWait(
[id = surface_id_, insets = kInsets,
bounds = kNormalBounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface,
SetWindowGeometry(gfx::Rect(
insets.left(), insets.top(),
bounds.width() - (insets.left() + insets.right()),
bounds.height() - (insets.top() + insets.bottom()))));
});
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Now, maximize, fullscreen and restore.
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
.WillRepeatedly(Return(gfx::Insets()));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
.Times(1);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(2000, 2000)));
});
states = InitializeWlArrayWithActivatedState();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
SendConfigureEvent(surface_id_, {2000, 2000}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kFullScreen))
.WillRepeatedly(Return(gfx::Insets()));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
.Times(1);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(2005, 2005)));
});
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
SendConfigureEvent(surface_id_, {2005, 2005}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Restore
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
.WillRepeatedly(Return(kInsets));
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
.Times(1);
PostToServerAndWait(
[id = surface_id_, insets = kInsets,
bounds = kNormalBounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface,
SetWindowGeometry(gfx::Rect(
insets.left(), insets.top(),
bounds.width() - (insets.left() + insets.right()),
bounds.height() - (insets.top() + insets.bottom()))));
});
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) {
constexpr gfx::Rect kNormalBounds{500, 300};
constexpr gfx::Rect kMaximizedBounds{800, 600};
// Make sure the window has normal state initially.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Deactivate the surface.
wl::ScopedWlArray empty_state({});
SendConfigureEvent(surface_id_, {0, 0}, empty_state);
auto active_maximized = MakeStateArray(
{XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
PostToServerAndWait([id = surface_id_, bounds = kMaximizedBounds](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
});
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Maximize();
// State changes are synchronous.
EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
// Verify that the state has not been changed.
EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetFullscreen());
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->SetFullscreen(true, display::kInvalidDisplayId);
// State changes are synchronous.
EXPECT_EQ(PlatformWindowState::kFullScreen,
window_->GetPlatformWindowState());
auto active_fullscreen = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED,
XDG_TOPLEVEL_STATE_MAXIMIZED,
XDG_TOPLEVEL_STATE_FULLSCREEN});
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_fullscreen);
AdvanceFrameToCurrent(window_.get(), delegate_);
// Verify that the state has not been changed.
EXPECT_EQ(PlatformWindowState::kFullScreen,
window_->GetPlatformWindowState());
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetFullscreen());
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Restore();
EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
AdvanceFrameToCurrent(window_.get(), delegate_);
// Verify that the state has not been changed.
EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
});
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Restore();
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
// Reinitialize wl_array, which removes previous old states.
auto active = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, active);
AdvanceFrameToCurrent(window_.get(), delegate_);
// Verify that the state has not been changed.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
}
TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximize) {
const gfx::Rect current_bounds = window_->GetBoundsInDIP();
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_TRUE(restored_bounds.IsEmpty());
gfx::Rect bounds = window_->GetBoundsInDIP();
constexpr gfx::Rect kMaximizedBounds(1024, 768);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->Maximize();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
// Both in XdgV5 and XdgV6, surfaces implement SetWindowGeometry method.
// Thus, using a toplevel object in XdgV6 case is not right thing. Use a
// surface here instead.
PostToServerAndWait(
[id = surface_id_, current_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface,
SetWindowGeometry(gfx::Rect{current_bounds.size()}));
});
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
bounds = window_->GetBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
}
TEST_P(WaylandWindowTest, RestoreBoundsAfterFullscreen) {
const gfx::Rect current_bounds = window_->GetBoundsInDIP();
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
gfx::Rect bounds = window_->GetBoundsInDIP();
constexpr gfx::Rect kFullscreenBounds(1280, 720);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetFullscreen(true, display::kInvalidDisplayId);
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
// Both in XdgV5 and XdgV6, surfaces implement SetWindowGeometry method.
// Thus, using a toplevel object in XdgV6 case is not right thing. Use a
// surface here instead.
PostToServerAndWait(
[id = surface_id_, current_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface,
SetWindowGeometry(gfx::Rect(current_bounds.size())));
});
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
bounds = window_->GetBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
}
TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) {
const gfx::Rect current_bounds = window_->GetBoundsInDIP();
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
gfx::Rect bounds = window_->GetBoundsInDIP();
constexpr gfx::Rect kMaximizedBounds(1024, 768);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->Maximize();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
constexpr gfx::Rect kFullscreenBounds(1280, 720);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetFullscreen(true, display::kInvalidDisplayId);
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
gfx::Rect fullscreen_restore_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, fullscreen_restore_bounds);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->Maximize();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, fullscreen_restore_bounds);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
// Both in XdgV5 and XdgV6, surfaces implement SetWindowGeometry method.
// Thus, using a toplevel object in XdgV6 case is not right thing. Use a
// surface here instead.
PostToServerAndWait(
[id = surface_id_, current_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface,
SetWindowGeometry(gfx::Rect(current_bounds.size())));
});
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, states);
AdvanceFrameToCurrent(window_.get(), delegate_);
bounds = window_->GetBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
}
TEST_P(WaylandWindowTest, SendsBoundsOnRequest) {
const gfx::Rect initial_bounds = window_->GetBoundsInDIP();
const gfx::Rect new_bounds =
gfx::Rect(initial_bounds.width() + 10, initial_bounds.height() + 10);
EXPECT_CALL(delegate_, OnBoundsChanged(kDefaultBoundsChange));
PostToServerAndWait([id = surface_id_,
new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(1);
});
window_->SetBoundsInDIP(new_bounds);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Restored bounds should keep empty value.
gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
}
TEST_P(WaylandWindowTest, UpdateWindowRegion) {
// Change bounds.
const gfx::Rect initial_bounds = window_->GetBoundsInDIP();
const gfx::Rect new_bounds =
gfx::Rect(initial_bounds.width() + 10, initial_bounds.height() + 10);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
});
window_->SetBoundsInDIP(new_bounds);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait(
[id = surface_id_, new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
EXPECT_EQ(mock_surface->input_region(), new_bounds);
});
// Maximize.
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
});
constexpr gfx::Rect kMaximizedBounds(1024, 768);
window_->Maximize();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_,
kMaximizedBounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_EQ(mock_surface->opaque_region(), kMaximizedBounds);
EXPECT_EQ(mock_surface->input_region(), kMaximizedBounds);
});
// Restore.
const gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
});
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
auto active = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, {0, 0}, active);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait(
[id = surface_id_, restored_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_EQ(mock_surface->opaque_region(), restored_bounds);
EXPECT_EQ(mock_surface->input_region(), restored_bounds);
});
AdvanceFrameToCurrent(window_.get(), delegate_);
}
TEST_P(WaylandWindowTest, CanDispatchMouseEventFocus) {
// SetPointerFocusedWindow requires a WaylandPointer.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
SetPointerFocusedWindow(window_.get());
Event::DispatcherApi(&test_mouse_event_).set_target(window_.get());
EXPECT_TRUE(window_->CanDispatchEvent(&test_mouse_event_));
}
TEST_P(WaylandWindowTest, SetCursorUsesCursorShapeForCommonTypes) {
SetPointerFocusedWindow(window_.get());
MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
// Verify some commonly-used cursors.
EXPECT_CALL(*mock_cursor_shape,
SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT));
auto pointer_cursor = AsPlatformCursor(
base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kPointer));
window_->SetCursor(pointer_cursor.get());
VerifyAndClearExpectations();
EXPECT_CALL(*mock_cursor_shape,
SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER));
auto hand_cursor = AsPlatformCursor(
base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kHand));
window_->SetCursor(hand_cursor.get());
VerifyAndClearExpectations();
EXPECT_CALL(*mock_cursor_shape,
SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT));
auto ibeam_cursor = AsPlatformCursor(
base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kIBeam));
window_->SetCursor(ibeam_cursor.get());
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, SetCursorCallsCursorShapeOncePerCursor) {
SetPointerFocusedWindow(window_.get());
MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
auto hand_cursor = AsPlatformCursor(
base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kHand));
// Setting the same cursor twice on the client only calls the server once.
EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(1);
window_->SetCursor(hand_cursor.get());
window_->SetCursor(hand_cursor.get());
}
TEST_P(WaylandWindowTest, SetCursorDoesNotUseCursorShapeForNoneCursor) {
SetPointerFocusedWindow(window_.get());
MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(0);
auto none_cursor = AsPlatformCursor(
base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kNone));
window_->SetCursor(none_cursor.get());
}
TEST_P(WaylandWindowTest, SetCursorDoesNotUseCursorShapeForCustomCursors) {
SetPointerFocusedWindow(window_.get());
MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
// Custom cursors require bitmaps, so they do not use server-side cursors.
EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(0);
auto custom_cursor = AsPlatformCursor(
base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kCustom, SkBitmap(),
gfx::Point(), kDefaultCursorScale));
window_->SetCursor(custom_cursor.get());
}
ACTION_P(CloneEvent, ptr) {
*ptr = arg0->Clone();
}
TEST_P(WaylandWindowTest, DispatchEvent) {
std::unique_ptr<Event> event;
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
window_->DispatchEvent(&test_mouse_event_);
ASSERT_TRUE(event);
ASSERT_TRUE(event->IsMouseEvent());
auto* mouse_event = event->AsMouseEvent();
EXPECT_EQ(mouse_event->location_f(), test_mouse_event_.location_f());
EXPECT_EQ(mouse_event->root_location_f(),
test_mouse_event_.root_location_f());
EXPECT_EQ(mouse_event->time_stamp(), test_mouse_event_.time_stamp());
EXPECT_EQ(mouse_event->button_flags(), test_mouse_event_.button_flags());
EXPECT_EQ(mouse_event->changed_button_flags(),
test_mouse_event_.changed_button_flags());
}
TEST_P(WaylandWindowTest, DispatchEventResult) {
// Create an arbitrary wayland window with a test delegate.
TestWaylandWindowDelegate window_delegate;
PlatformWindowInitProperties properties(gfx::Rect(10, 10));
auto window = WaylandWindow::Create(&window_delegate, connection_.get(),
std::move(properties));
KeyEvent event_1(EventType::kKeyPressed, VKEY_0, 0);
window_delegate.SetDispatchEventCallback(
base::BindOnce([](Event* event) { event->SetSkipped(); }));
EXPECT_EQ(window->DispatchEvent(&event_1), POST_DISPATCH_PERFORM_DEFAULT);
KeyEvent event_2(EventType::kKeyPressed, VKEY_0, 0);
window_delegate.SetDispatchEventCallback(
base::BindOnce([](Event* event) { event->StopPropagation(); }));
EXPECT_EQ(window->DispatchEvent(&event_2), POST_DISPATCH_STOP_PROPAGATION);
KeyEvent event_3(EventType::kKeyPressed, VKEY_0, 0);
window_delegate.SetDispatchEventCallback(
base::BindOnce([](Event* event) { event->SetHandled(); }));
EXPECT_EQ(window->DispatchEvent(&event_3), POST_DISPATCH_STOP_PROPAGATION);
KeyEvent event_4(EventType::kKeyPressed, VKEY_0, 0);
window_delegate.SetDispatchEventCallback(base::BindOnce([](Event* event) {
// Do nothing.
}));
EXPECT_EQ(window->DispatchEvent(&event_4), POST_DISPATCH_NONE);
}
TEST_P(WaylandWindowTest, ConfigureEvent) {
wl::ScopedWlArray states({});
// The surface must react on each configure event and send bounds to its
// delegate.
constexpr gfx::Size kSize{1000, 1000};
uint32_t serial = 12u;
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
// Responding to a configure event, the window geometry in here must respect
// the sizing negotiations specified by the configure event.
// |xdg_surface| must receive the following calls in both xdg_shell_v5 and
// xdg_shell_v6. Other calls like SetTitle or SetMaximized are received by
// xdg_toplevel in xdg_shell_v6 and by xdg_surface in xdg_shell_v5.
PostToServerAndWait(
[id = surface_id_, kSize, serial](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(kSize))).Times(1);
EXPECT_CALL(*xdg_surface, AckConfigure(serial));
});
SendConfigureEvent(surface_id_, kSize, states, serial);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
constexpr gfx::Size kNewSize{1500, 1000};
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
PostToServerAndWait([id = surface_id_, kNewSize,
serial](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(kNewSize))).Times(1);
EXPECT_CALL(*xdg_surface, AckConfigure(serial + 1));
});
SendConfigureEvent(surface_id_, kNewSize, states, ++serial);
AdvanceFrameToCurrent(window_.get(), delegate_);
}
TEST_P(WaylandWindowTest, ConfigureEventWithNulledSize) {
wl::ScopedWlArray states({});
// |xdg_surface| must receive the following calls in both xdg_shell_v5 and
// xdg_shell_v6. Other calls like SetTitle or SetMaximized are received by
// xdg_toplevel in xdg_shell_v6 and by xdg_surface in xdg_shell_v5.
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, AckConfigure(14u));
});
// If Wayland sends configure event with 0 width and 0 size, client should
// call back with desired sizes. In this case, that's the actual size of
// the window.
SendConfigureEvent(surface_id_, {0, 0}, states, 14u);
}
TEST_P(WaylandWindowTest, ConfigureEventIsNotAckedMultipleTimes) {
constexpr gfx::Rect kNormalBounds{500, 300};
constexpr gfx::Rect kSecondBounds{600, 600};
// Configure event makes Wayland update bounds, but does not change toplevel
// input region, opaque region or window geometry immediately. Such actions
// are postponed to OnSequencePoint();
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
});
auto state = InitializeWlArrayWithActivatedState();
constexpr uint32_t kConfigureSerial = 2u;
SendConfigureEvent(surface_id_, kNormalBounds.size(), state,
kConfigureSerial);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial));
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
EXPECT_CALL(*mock_surface, SetInputRegion(_));
});
// Get the viz sequence ID which will let us ack the configure event.
auto viz_seq = delegate_.viz_seq();
// Insert another change which will increase the viz sequence ID here.
// We want to make sure that when this viz sequence ID is reached, we
// don't send another ack for the configure.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetBoundsInDIP(kSecondBounds);
AdvanceFrameToGivenVizSequenceId(window_.get(), delegate_, viz_seq);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_, bounds = kSecondBounds](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
EXPECT_CALL(*mock_surface, SetInputRegion(_));
});
AdvanceFrameToCurrent(window_.get(), delegate_);
}
TEST_P(WaylandWindowTest, ManyConfigureEventsDoesNotCrash) {
constexpr uint32_t kConfigureSerial = 2u;
auto state = InitializeWlArrayWithActivatedState();
gfx::Size size{500, 300};
for (int i = 0; i < 3000; ++i) {
SendConfigureEvent(surface_id_, size, state, kConfigureSerial + i);
size.Enlarge(1, 1);
}
AdvanceFrameToCurrent(window_.get(), delegate_);
}
// If the server immediately changes the bounds after a window is initialised,
// make sure that the client doesn't wait for a new frame to be produced.
// See https://crbug.com/1427954.
TEST_P(WaylandWindowTest, InitialConfigureFollowedByBoundsChangeCompletesAck) {
constexpr gfx::Rect kFirstBounds{0, 0, 800, 600};
constexpr gfx::Rect kSecondBounds{50, 50, 800, 600};
constexpr uint32_t kConfigureSerial = 2u;
// Make sure that we start off with the initial bounds we expect.
EXPECT_EQ(kFirstBounds, window_->latched_state().bounds_dip);
EXPECT_EQ(kFirstBounds, window_->applied_state().bounds_dip);
// Expect an origin change.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange{true})));
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial)).Times(1);
});
{
WaylandWindow::WindowStates window_states;
window_states.is_activated = true;
window_->HandleToplevelConfigureWithOrigin(
kSecondBounds.x(), kSecondBounds.y(), kSecondBounds.width(),
kSecondBounds.height(), window_states);
}
window_->HandleSurfaceConfigure(kConfigureSerial);
// Don't call AdvanceFrameToCurrent here, because just updating the
// bounds origin won't generate a new frame. The client should ack
// even if there is no new frame produced.
}
TEST_P(WaylandWindowTest, OnActivationChanged) {
uint32_t serial = 0;
MockWaylandPlatformWindowDelegate new_window_delegate(connection_.get());
auto new_window = CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, gfx::Rect(100, 100), &new_window_delegate);
ASSERT_TRUE(new_window);
auto new_window_surface_id = new_window->root_surface()->get_surface_id();
MockWaylandPlatformWindowDelegate menu_delegate(connection_.get());
auto menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(100, 100), &menu_delegate,
new_window->GetWidget());
ASSERT_TRUE(menu);
wl::ScopedWlArray empty_state({});
SendConfigureEvent(surface_id_, {0, 0}, empty_state, ++serial);
SendConfigureEvent(new_window_surface_id, {0, 0}, empty_state, ++serial);
// Request active decorations.
wl::ScopedWlArray active_state = InitializeWlArrayWithActivatedState();
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
SendConfigureEvent(surface_id_, {0, 0}, active_state, ++serial);
VerifyAndClearExpectations();
// Plug keyboard.
EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_KEYBOARD);
});
VerifyAndClearExpectations();
// Focus keyboard.
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
SetKeyboardFocusedWindow(window_.get());
VerifyAndClearExpectations();
// Switch keyboard focus to other window.
EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
EXPECT_CALL(new_window_delegate, OnActivationChanged(Eq(true)));
SetKeyboardFocusedWindow(new_window.get());
VerifyAndClearExpectations();
// Switch keyboard focus to menu in other window.
EXPECT_CALL(new_window_delegate, OnActivationChanged(_)).Times(0);
SetKeyboardFocusedWindow(menu.get());
VerifyAndClearExpectations();
// Unfocus keyboard.
EXPECT_CALL(new_window_delegate, OnActivationChanged(Eq(false)));
SetKeyboardFocusedWindow(nullptr);
VerifyAndClearExpectations();
// Unplug keyboard - should fall back to xdg-shell activated state.
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(), 0);
});
VerifyAndClearExpectations();
// Request inactive decorations.
EXPECT_CALL(delegate_, OnActivationChanged(false));
SendConfigureEvent(surface_id_, {0, 0}, empty_state, ++serial);
}
TEST_P(WaylandWindowTest, OnAcceleratedWidgetDestroy) {
window_.reset();
}
TEST_P(WaylandWindowTest, CanCreateMenuWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
// SetPointerFocus(true) requires a WaylandPointer.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(
server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH);
});
ASSERT_TRUE(connection_->seat()->pointer());
ASSERT_TRUE(connection_->seat()->touch());
SetPointerFocusedWindow(window_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate);
EXPECT_TRUE(menu_window);
SetPointerFocusedWindow(window_.get());
window_->set_touch_focus(false);
// Given that there is no parent passed and we don't have any focused windows,
// Wayland must still create a window.
menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate);
EXPECT_TRUE(menu_window);
window_->set_touch_focus(true);
menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate);
EXPECT_TRUE(menu_window);
}
TEST_P(WaylandWindowTest, CreateAndDestroyNestedMenuWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
gfx::AcceleratedWidget menu_window_widget;
EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_))
.WillOnce(SaveArg<0>(&menu_window_widget));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate,
widget_);
EXPECT_TRUE(menu_window);
ASSERT_NE(menu_window_widget, gfx::kNullAcceleratedWidget);
MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
connection_.get());
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(20, 0, 10, 10),
&nested_menu_window_delegate, menu_window_widget);
EXPECT_TRUE(nested_menu_window);
}
TEST_P(WaylandWindowTest, DispatchesLocatedEventsToCapturedWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10, 10, 10),
&menu_window_delegate, widget_);
EXPECT_TRUE(menu_window);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
SetPointerFocusedWindow(window_.get());
// Make sure the events are handled by the window that has the pointer focus.
VerifyCanDispatchMouseEvents(window_.get(), {menu_window.get()});
// The |window_| that has the pointer focus must receive the event.
EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event;
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// The event is sent in local surface coordinates of the |window|.
wl_pointer_send_motion(server->seat()->pointer()->resource(),
server->GetNextSerial(), wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server->seat()->pointer()->resource());
});
ASSERT_TRUE(event->IsLocatedEvent());
EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 20));
// Set capture to menu window now.
menu_window->SetCapture();
// It's still the |window_| that can dispatch the events, but it will reroute
// the event to correct window and fix the location.
VerifyCanDispatchMouseEvents(window_.get(), {menu_window.get()});
// The |window_| that has the pointer focus must receive the event.
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event2;
EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
.WillOnce(CloneEvent(&event2));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// The event is sent in local surface coordinates of the |window|.
wl_pointer_send_motion(server->seat()->pointer()->resource(),
server->GetNextSerial(), wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server->seat()->pointer()->resource());
});
ASSERT_TRUE(event2->IsLocatedEvent());
EXPECT_EQ(event2->AsLocatedEvent()->location(), gfx::Point(0, 10));
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event3;
EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
.WillOnce(CloneEvent(&event3));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// The event is sent in local surface coordinates of the |window|.
wl_pointer_send_motion(server->seat()->pointer()->resource(),
server->GetNextSerial(), wl_fixed_from_double(2.75),
wl_fixed_from_double(8.375));
wl_pointer_send_frame(server->seat()->pointer()->resource());
});
ASSERT_TRUE(event3->IsLocatedEvent());
EXPECT_EQ(event3->AsLocatedEvent()->location(), gfx::Point(-8, -2));
// If nested menu window is added, the events are still correctly translated
// to the captured window.
MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
connection_.get());
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(15, 18, 10, 10),
&nested_menu_window_delegate, menu_window->GetWidget());
EXPECT_TRUE(nested_menu_window);
SetPointerFocusedWindow(nested_menu_window.get());
// The event is processed by the window that has the pointer focus, but
// dispatched by the window that has the capture.
VerifyCanDispatchMouseEvents(nested_menu_window.get(),
{window_.get(), menu_window.get()});
EXPECT_TRUE(menu_window->HasCapture());
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
EXPECT_CALL(nested_menu_window_delegate, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event4;
EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
.WillOnce(CloneEvent(&event4));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// The event is sent in local surface coordinates of the
// |nested_menu_window|.
wl_pointer_send_motion(server->seat()->pointer()->resource(),
server->GetNextSerial(), wl_fixed_from_double(2.75),
wl_fixed_from_double(8.375));
wl_pointer_send_frame(server->seat()->pointer()->resource());
});
ASSERT_TRUE(event4->IsLocatedEvent());
EXPECT_EQ(event4->AsLocatedEvent()->location(), gfx::Point(7, 16));
nested_menu_window.reset();
menu_window.reset();
}
// Verify that located events are translated correctly when the windows have
// geometry with non-zero offset.
// See https://crbug.com/1292486.
TEST_P(WaylandWindowTest, ConvertEventToTarget) {
constexpr gfx::Rect kMainWindowBounds{956, 556};
const auto kMainWindowInsets = gfx::Insets::TLBR(24, 28, 32, 28);
auto bounds_with_insets = kMainWindowBounds;
bounds_with_insets.Inset(kMainWindowInsets);
EXPECT_CALL(delegate_, OnBoundsChanged(_));
EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
.WillRepeatedly(Return(kMainWindowInsets));
PostToServerAndWait([id = surface_id_, bounds_with_insets](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds_with_insets));
});
window_->SetBoundsInDIP(kMainWindowBounds);
SendConfigureEvent(surface_id_, bounds_with_insets.size(),
MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED}));
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Create a menu.
constexpr gfx::Rect kMenuBounds{100, 100, 80, 50};
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, kMenuBounds, &menu_window_delegate, widget_);
EXPECT_TRUE(menu_window);
// Now translate the event located at (0, 0) in the parent window into the
// coordinate system of the menu. Its coordinates must be equal to:
// -(offset of the menu).
constexpr gfx::PointF kParentPoint{0, 0};
ui::MouseEvent event(ui::EventType::kMouseMoved, kParentPoint, kParentPoint,
{}, ui::EF_NONE, ui::EF_NONE);
ui::Event::DispatcherApi dispatcher_api(&event);
dispatcher_api.set_target(window_.get());
ui::WaylandEventSource::ConvertEventToTarget(menu_window.get(), &event);
EXPECT_EQ(event.AsLocatedEvent()->x(), -kMenuBounds.x());
EXPECT_EQ(event.AsLocatedEvent()->y(), -kMenuBounds.y());
}
// Tests that the event grabber gets the events processed by its toplevel parent
// window iff they belong to the same "family". Otherwise, events mustn't be
// rerouted from another toplevel window to the event grabber.
TEST_P(WaylandWindowTest,
DispatchesLocatedEventsToCapturedWindowInTheSameStack) {
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(30, 40, 20, 50),
&menu_window_delegate, widget_);
EXPECT_TRUE(menu_window);
// Second toplevel window has the same bounds as the |window_|.
MockWaylandPlatformWindowDelegate toplevel_window2_delegate(
connection_.get());
std::unique_ptr<WaylandWindow> toplevel_window2 =
CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
window_->GetBoundsInDIP(),
&toplevel_window2_delegate);
EXPECT_TRUE(toplevel_window2);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
SetPointerFocusedWindow(window_.get());
// Make sure the events are handled by the window that has the pointer focus.
VerifyCanDispatchMouseEvents(window_.get(),
{menu_window.get(), toplevel_window2.get()});
menu_window->SetCapture();
// The |menu_window| that has capture must receive the event.
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
EXPECT_CALL(toplevel_window2_delegate, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event;
EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
.WillOnce(CloneEvent(&event));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// The event is sent in local surface coordinates of the |window|.
wl_pointer_send_motion(server->seat()->pointer()->resource(),
server->GetNextSerial(), wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server->seat()->pointer()->resource());
});
ASSERT_TRUE(event->IsLocatedEvent());
EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(-20, -20));
// Now, pretend that the second toplevel window gets the pointer focus - the
// event grabber must be disragerder now.
SetPointerFocusedWindow(toplevel_window2.get());
VerifyCanDispatchMouseEvents(toplevel_window2.get(),
{menu_window.get(), window_.get()});
// The |toplevel_window2| that has capture and must receive the event.
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
event.reset();
EXPECT_CALL(toplevel_window2_delegate, DispatchEvent(_))
.WillOnce(CloneEvent(&event));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// The event is sent in local surface coordinates of the |toplevel_window2|
// (they're basically the same as the |window| has.)
wl_pointer_send_motion(server->seat()->pointer()->resource(),
server->GetNextSerial(), wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server->seat()->pointer()->resource());
});
ASSERT_TRUE(event->IsLocatedEvent());
EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 20));
}
TEST_P(WaylandWindowTest, DispatchesKeyboardEventToToplevelWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10, 10, 10),
&menu_window_delegate, widget_);
EXPECT_TRUE(menu_window);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_KEYBOARD);
});
ASSERT_TRUE(connection_->seat()->keyboard());
SetKeyboardFocusedWindow(menu_window.get());
// Disable auto-repeat so that it doesn't ruin our expectations.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_keyboard_send_repeat_info(server->seat()->keyboard()->resource(), 0, 0);
});
// Even though the menu window has the keyboard focus, the keyboard events are
// dispatched by the root parent wayland window in the end.
VerifyCanDispatchKeyEvents({menu_window.get()}, {window_.get()});
EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event;
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_keyboard_send_key(server->seat()->keyboard()->resource(),
server->GetNextSerial(), server->GetNextTime(),
30 /* a */, WL_KEYBOARD_KEY_STATE_PRESSED);
});
ASSERT_TRUE(event->IsKeyEvent());
VerifyAndClearExpectations();
EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
event.reset();
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_keyboard_send_key(server->seat()->keyboard()->resource(),
server->GetNextSerial(), server->GetNextTime(),
30 /* a */, WL_KEYBOARD_KEY_STATE_RELEASED);
});
ASSERT_TRUE(event->IsKeyEvent());
// Setting capture doesn't affect the kbd events.
menu_window->SetCapture();
VerifyCanDispatchKeyEvents({menu_window.get()}, {window_.get()});
EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
event.reset();
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_keyboard_send_key(server->seat()->keyboard()->resource(),
server->GetNextSerial(), server->GetNextTime(),
30 /* a */, WL_KEYBOARD_KEY_STATE_PRESSED);
});
ASSERT_TRUE(event->IsKeyEvent());
VerifyAndClearExpectations();
menu_window.reset();
}
// Tests that event is processed by the surface that has the focus. More
// extensive tests are located in wayland touch/keyboard/pointer unittests.
TEST_P(WaylandWindowTest, CanDispatchEvent) {
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
gfx::AcceleratedWidget menu_window_widget;
EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_))
.WillOnce(SaveArg<0>(&menu_window_widget));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate,
widget_);
EXPECT_TRUE(menu_window);
MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
connection_.get());
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(20, 0, 10, 10),
&nested_menu_window_delegate, menu_window_widget);
EXPECT_TRUE(nested_menu_window);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER |
WL_SEAT_CAPABILITY_KEYBOARD |
WL_SEAT_CAPABILITY_TOUCH);
});
ASSERT_TRUE(connection_->seat()->pointer());
ASSERT_TRUE(connection_->seat()->touch());
ASSERT_TRUE(connection_->seat()->keyboard());
// Test that CanDispatchEvent is set correctly.
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
wl_pointer_send_enter(server->seat()->pointer()->resource(),
server->GetNextSerial(), toplevel_surface->resource(),
0, 0);
});
// Only |window_| can dispatch MouseEvents.
VerifyCanDispatchMouseEvents(window_.get(),
{menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchTouchEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchKeyEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
wl::ScopedWlArray empty({});
wl_keyboard_send_enter(server->seat()->keyboard()->resource(),
server->GetNextSerial(),
toplevel_surface->resource(), empty.get());
});
// Only |window_| can dispatch MouseEvents and KeyEvents.
VerifyCanDispatchMouseEvents(window_.get(),
{menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchTouchEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchKeyEvents({window_.get()},
{menu_window.get(), nested_menu_window.get()});
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
wl_touch_send_down(server->seat()->touch()->resource(),
server->GetNextSerial(), 0, toplevel_surface->resource(),
0 /* id */, wl_fixed_from_int(50),
wl_fixed_from_int(100));
wl_touch_send_frame(server->seat()->touch()->resource());
});
// Only |window_| can dispatch MouseEvents and KeyEvents.
VerifyCanDispatchMouseEvents(window_.get(),
{menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchTouchEvents({window_.get()},
{menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchKeyEvents({window_.get()},
{menu_window.get(), nested_menu_window.get()});
const uint32_t menu_window_surface_id =
menu_window->root_surface()->get_surface_id();
PostToServerAndWait(
[toplevel_surface_id = surface_id_,
menu_window_surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface =
server->GetObject<wl::MockSurface>(toplevel_surface_id);
wl::MockSurface* menu_window_surface =
server->GetObject<wl::MockSurface>(menu_window_surface_id);
wl_pointer_send_leave(server->seat()->pointer()->resource(),
server->GetNextSerial(),
toplevel_surface->resource());
wl_pointer_send_enter(server->seat()->pointer()->resource(),
server->GetNextSerial(),
menu_window_surface->resource(), 0, 0);
wl_touch_send_up(server->seat()->touch()->resource(),
server->GetNextSerial(), 1000, 0 /* id */);
wl_touch_send_frame(server->seat()->touch()->resource());
wl_keyboard_send_leave(server->seat()->keyboard()->resource(),
server->GetNextSerial(),
toplevel_surface->resource());
});
// Only |menu_window| can dispatch MouseEvents.
VerifyCanDispatchMouseEvents(menu_window.get(),
{window_.get(), nested_menu_window.get()});
VerifyCanDispatchTouchEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchKeyEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
const uint32_t nested_menu_window_surface_id =
nested_menu_window->root_surface()->get_surface_id();
PostToServerAndWait([nested_menu_window_surface_id, menu_window_surface_id](
wl::TestWaylandServerThread* server) {
wl::MockSurface* menu_window_surface =
server->GetObject<wl::MockSurface>(menu_window_surface_id);
wl::MockSurface* nested_menu_window_surface =
server->GetObject<wl::MockSurface>(nested_menu_window_surface_id);
wl_pointer_send_leave(server->seat()->pointer()->resource(),
server->GetNextSerial(),
menu_window_surface->resource());
wl_pointer_send_enter(server->seat()->pointer()->resource(),
server->GetNextSerial(),
nested_menu_window_surface->resource(), 0, 0);
});
// Only |nested_menu_window| can dispatch MouseEvents.
VerifyCanDispatchMouseEvents(nested_menu_window.get(),
{window_.get(), menu_window.get()});
VerifyCanDispatchTouchEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
VerifyCanDispatchKeyEvents(
{}, {window_.get(), menu_window.get(), nested_menu_window.get()});
}
TEST_P(WaylandWindowTest, DispatchWindowMove) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
// Focus and press left mouse button, so that serial is sent to client.
auto* pointer_resource = server->seat()->pointer()->resource();
wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
surface->resource(), 0, 0);
wl_pointer_send_button(pointer_resource, server->GetNextSerial(),
server->GetNextTime(), BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
});
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
auto* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), Move(_));
});
ui::GetWmMoveResizeHandler(*window_)->DispatchHostWindowDragMovement(
HTCAPTION, gfx::Point());
}
// Makes sure hit tests are converted into right edges.
TEST_P(WaylandWindowTest, DispatchWindowResize) {
std::vector<int> hit_test_values;
InitializeWithSupportedHitTestValues(&hit_test_values);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
// Focus and press left mouse button, so that serial is sent to client.
auto* pointer_resource = server->seat()->pointer()->resource();
wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
surface->resource(), 0, 0);
wl_pointer_send_button(pointer_resource, server->GetNextSerial(),
server->GetNextTime(), BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
});
auto* wm_move_resize_handler = ui::GetWmMoveResizeHandler(*window_);
for (const int value : hit_test_values) {
{
uint32_t direction = wl::IdentifyDirection(value);
PostToServerAndWait(
[id = surface_id_, direction](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
auto* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface->xdg_toplevel(), Resize(_, Eq(direction)));
});
wm_move_resize_handler->DispatchHostWindowDragMovement(value,
gfx::Point());
}
}
}
TEST_P(WaylandWindowTest, ToplevelWindowUpdateWindowScale) {
VerifyAndClearExpectations();
// Surface scale must be 1 when no output has been entered by the window.
EXPECT_EQ(1, window_->applied_state().window_scale);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Configure first output with scale 1.
wl::TestOutput* output1 = server->output();
output1->SetPhysicalAndLogicalBounds({1920, 1080});
output1->Flush();
// Creating an output with scale 2.
auto* output2 =
server->CreateAndInitializeOutput(wl::TestOutputMetrics({1920, 1080}));
output2->SetScale(2);
});
WaitForAllDisplaysReady();
auto* output_manager = connection_->wayland_output_manager();
EXPECT_EQ(2u, output_manager->GetAllOutputs().size());
const uint32_t output_id1 =
GetObjIdForOutput(output_manager->GetAllOutputs().begin()->first);
PostToServerAndWait(
[id = surface_id_, output_id1](wl::TestWaylandServerThread* server) {
wl::TestOutput* output = server->GetObject<wl::TestOutput>(output_id1);
ASSERT_TRUE(output);
// Send the window to |output1|.
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), output->resource());
});
// The window's scale and bounds must remain unchanged.
EXPECT_EQ(1, window_->applied_state().window_scale);
EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
// Get another output's id.
const uint32_t output_id2 =
GetObjIdForOutput(output_manager->GetAllOutputs().rbegin()->first);
PostToServerAndWait([id = surface_id_, output_id2,
output_id1](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(output_id2);
ASSERT_TRUE(output2);
wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(output_id1);
ASSERT_TRUE(output1);
// Simulating drag process from |output1| to |output2|.
wl_surface_send_enter(surface->resource(), output2->resource());
wl_surface_send_leave(surface->resource(), output1->resource());
});
// The window must change its scale and bounds to keep DIP bounds the same.
EXPECT_EQ(2, window_->applied_state().window_scale);
EXPECT_EQ(gfx::Size(1600, 1200), window_->applied_state().size_px);
EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
}
TEST_P(WaylandWindowTest, WaylandPopupSurfaceScale) {
VerifyAndClearExpectations();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Configure an output with scale 1.
wl::TestOutput* output1 = server->output();
output1->SetPhysicalAndLogicalBounds({1920, 1080});
output1->Flush();
// Creating an output with scale 2.
auto* output2 = server->CreateAndInitializeOutput(
wl::TestOutputMetrics({1920, 0, 1920, 1080}));
output2->SetScale(2);
});
WaitForAllDisplaysReady();
auto* output_manager = connection_->wayland_output_manager();
EXPECT_EQ(2u, output_manager->GetAllOutputs().size());
const uint32_t output_id1 =
GetObjIdForOutput(output_manager->GetAllOutputs().begin()->first);
const uint32_t output_id2 =
GetObjIdForOutput(output_manager->GetAllOutputs().rbegin()->first);
std::vector<PlatformWindowType> window_types{PlatformWindowType::kMenu,
PlatformWindowType::kTooltip};
for (const auto& type : window_types) {
PostToServerAndWait([id = surface_id_,
output_id1](wl::TestWaylandServerThread* server) {
// Send the window to |output1|.
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output = server->GetObject<wl::TestOutput>(output_id1);
wl_surface_send_enter(surface->resource(), output->resource());
});
// Creating a wayland_popup on |window_|.
SetPointerFocusedWindow(window_.get());
gfx::Rect wayland_popup_bounds(15, 15, 10, 10);
auto wayland_popup = CreateWaylandWindowWithParams(
type, wayland_popup_bounds, &delegate_, window_->GetWidget());
EXPECT_TRUE(wayland_popup);
wayland_popup->Show(false);
// the wayland_popup window should inherit its buffer scale from the focused
// window.
EXPECT_EQ(1, window_->applied_state().window_scale);
EXPECT_EQ(window_->applied_state().window_scale,
wayland_popup->applied_state().window_scale);
EXPECT_EQ(wayland_popup_bounds.size(),
wayland_popup->applied_state().size_px);
EXPECT_EQ(wayland_popup_bounds, wayland_popup->GetBoundsInDIP());
wayland_popup->Hide();
PostToServerAndWait([id = surface_id_, output_id1,
output_id2](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(output_id1);
wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(output_id2);
// Send the window to |output2|.
wl_surface_send_enter(surface->resource(), output2->resource());
wl_surface_send_leave(surface->resource(), output1->resource());
});
EXPECT_EQ(2, window_->applied_state().window_scale);
wayland_popup->Show(false);
WaylandTestBase::SyncDisplay();
// |wayland_popup|'s scale and bounds must change whenever its parents
// scale is changed.
EXPECT_EQ(window_->applied_state().window_scale,
wayland_popup->applied_state().window_scale);
EXPECT_EQ(
gfx::ScaleToCeiledSize(wayland_popup_bounds.size(),
wayland_popup->applied_state().window_scale),
wayland_popup->applied_state().size_px);
wayland_popup->Hide();
SetPointerFocusedWindow(nullptr);
PostToServerAndWait([id = surface_id_,
output_id2](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(output_id2);
wl_surface_send_leave(surface->resource(), output2->resource());
});
}
}
// Tests that WaylandPopup is able to translate provided bounds via
// PlatformWindowProperties using buffer scale it's going to use that the client
// is not able to determine before PlatformWindow is created. See
// WaylandPopup::OnInitialize for more details.
TEST_P(WaylandWindowTest, WaylandPopupInitialBufferScale) {
VerifyAndClearExpectations();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Configure an output with scale 1.
wl::TestOutput* main_output = server->output();
main_output->SetPhysicalAndLogicalBounds({1920, 1080});
main_output->Flush();
// Creating an output with scale 1.
server->CreateAndInitializeOutput(
wl::TestOutputMetrics({1921, 0, 1920, 1080}));
});
auto* output_manager = connection_->wayland_output_manager();
EXPECT_EQ(2u, output_manager->GetAllOutputs().size());
const WaylandOutput* main_output =
output_manager->GetAllOutputs().begin()->second.get();
const WaylandOutput* secondary_output =
output_manager->GetAllOutputs().rbegin()->second.get();
struct {
raw_ptr<const WaylandOutput> output;
const char* label;
} screen[] = {{main_output, "main output"},
{secondary_output, "secondary output"}};
for (const auto& entered_output : screen) {
PostToServerAndWait(
[id = surface_id_, entered_output_id = GetObjIdForOutput(
entered_output.output->output_id())](
wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output =
server->GetObject<wl::TestOutput>(entered_output_id);
wl_surface_send_enter(surface->resource(), output->resource());
});
for (auto main_output_scale = 1; main_output_scale < 5;
main_output_scale++) {
for (auto secondary_output_scale = 1; secondary_output_scale < 5;
secondary_output_scale++) {
PostToServerAndWait(
[main_output_id = GetObjIdForOutput(main_output->output_id()),
secondary_output_id =
GetObjIdForOutput(secondary_output->output_id()),
main_output_scale,
secondary_output_scale](wl::TestWaylandServerThread* server) {
wl::TestOutput* main_output =
server->GetObject<wl::TestOutput>(main_output_id);
wl::TestOutput* secondary_output =
server->GetObject<wl::TestOutput>(secondary_output_id);
// Update scale factors first.
main_output->SetScale(main_output_scale);
secondary_output->SetScale(secondary_output_scale);
main_output->Flush();
secondary_output->Flush();
});
gfx::Rect bounds_dip(15, 15, 10, 10);
// DesktopWindowTreeHostPlatform uses the scale of the current display
// of the parent window to translate initial bounds of the popup to
// pixels.
const int32_t effective_scale = entered_output.output->scale_factor();
gfx::Transform transform;
transform.Scale(effective_scale, effective_scale);
gfx::Rect wayland_popup_bounds = transform.MapRect(bounds_dip);
std::unique_ptr<WaylandWindow> wayland_popup =
CreateWaylandWindowWithParams(PlatformWindowType::kMenu, bounds_dip,
&delegate_, window_->GetWidget());
EXPECT_TRUE(wayland_popup);
wayland_popup->Show(false);
gfx::Size expected_px_size = wayland_popup_bounds.size();
if (entered_output.output == secondary_output) {
expected_px_size =
gfx::ScaleToCeiledSize(bounds_dip.size(), secondary_output_scale);
}
EXPECT_EQ(expected_px_size, wayland_popup->applied_state().size_px)
<< " when the window is on " << entered_output.label
<< " that has scale " << entered_output.output->scale_factor();
}
}
PostToServerAndWait(
[id = surface_id_,
entered_output_id = GetObjIdForOutput(main_output->output_id())](
wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output =
server->GetObject<wl::TestOutput>(entered_output_id);
wl_surface_send_leave(surface->resource(), output->resource());
});
}
}
TEST_P(WaylandWindowTest, WaylandPopupInitialBufferUsesParentScale) {
VerifyAndClearExpectations();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Configure the the first output with scale 1.
wl::TestOutput* main_output = server->output();
main_output->SetPhysicalAndLogicalBounds({1920, 1080});
main_output->Flush();
// Creating an output with scale 2.
auto* output2 = server->CreateAndInitializeOutput(
wl::TestOutputMetrics({1921, 0, 1920, 1080}));
output2->SetScale(2);
});
WaitForAllDisplaysReady();
auto* output_manager = connection_->wayland_output_manager();
EXPECT_EQ(2u, output_manager->GetAllOutputs().size());
const uint32_t secondary_output_id =
GetObjIdForOutput(output_manager->GetAllOutputs().rbegin()->first);
PostToServerAndWait([id = surface_id_, secondary_output_id](
wl::TestWaylandServerThread* server) {
// Send the window to |output2|.
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output =
server->GetObject<wl::TestOutput>(secondary_output_id);
wl_surface_send_enter(surface->resource(), output->resource());
});
constexpr gfx::Rect kBoundsDip{50, 50, 100, 100};
const gfx::Size expected_size_px =
gfx::ScaleToCeiledSize(kBoundsDip.size(), 2);
std::unique_ptr<WaylandWindow> wayland_popup = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, kBoundsDip, &delegate_, window_->GetWidget());
EXPECT_TRUE(wayland_popup);
wayland_popup->Show(false);
EXPECT_EQ(expected_size_px, wayland_popup->applied_state().size_px);
PostToServerAndWait([id = surface_id_, secondary_output_id](
wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::TestOutput* output =
server->GetObject<wl::TestOutput>(secondary_output_id);
wl_surface_send_leave(surface->resource(), output->resource());
});
}
// Tests that a WaylandWindow uses the entered output with largest scale
// factor as the preferred output. If scale factors are equal, the very first
// entered display is used.
TEST_P(WaylandWindowTest, GetPreferredOutput) {
VerifyAndClearExpectations();
// Buffer scale must be 1 when no output has been entered by the window.
EXPECT_EQ(1, window_->applied_state().window_scale);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Update first output.
wl::TestOutput* output1 = server->output();
output1->SetPhysicalAndLogicalBounds({1920, 1080});
output1->Flush();
// Creating a 2nd output.
server->CreateAndInitializeOutput(
wl::TestOutputMetrics({1921, 0, 1920, 1080}));
});
WaitForAllDisplaysReady();
// Client side WaylandOutput ids.
ASSERT_EQ(2u, screen_->GetAllDisplays().size());
const uint32_t output1_id =
screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(0).id());
const uint32_t output2_id =
screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(1).id());
// Client side surface.
WaylandSurface* wayland_surface = window_->root_surface();
EXPECT_THAT(wayland_surface->entered_outputs(), ElementsAre());
auto* output_manager = connection_->wayland_output_manager();
ASSERT_EQ(2u, output_manager->GetAllOutputs().size());
// Shared wl_output resource ids.
const uint32_t wl_output1_id = GetObjIdForOutput(output1_id);
const uint32_t wl_output2_id = GetObjIdForOutput(output2_id);
PostToServerAndWait([id = surface_id_, wl_output1_id,
wl_output2_id](wl::TestWaylandServerThread* server) {
// Send the window to |output1| and |output2|.
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(id)->resource(),
server->GetObject<wl::TestOutput>(wl_output1_id)->resource());
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(id)->resource(),
server->GetObject<wl::TestOutput>(wl_output2_id)->resource());
});
// The window entered two outputs.
EXPECT_THAT(wayland_surface->entered_outputs(),
ElementsAre(output1_id, output2_id));
// The window must prefer the output that it entered first.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Create the third output.
server->CreateAndInitializeOutput(
wl::TestOutputMetrics({0, 1081, 1920, 1080}));
});
WaitForAllDisplaysReady();
ASSERT_EQ(3u, screen_->GetAllDisplays().size());
const uint32_t output3_id =
screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(2).id());
EXPECT_EQ(3u, output_manager->GetAllOutputs().size());
const uint32_t wl_output3_id = GetObjIdForOutput(output3_id);
PostToServerAndWait(
[id = surface_id_, wl_output3_id](wl::TestWaylandServerThread* server) {
// Send window into 3rd output.
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(id)->resource(),
server->GetObject<wl::TestOutput>(wl_output3_id)->resource());
});
// The window entered three outputs...
EXPECT_THAT(wayland_surface->entered_outputs(),
ElementsAre(output1_id, output2_id, output3_id));
// but it still must prefer the output that it entered first.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
PostToServerAndWait([wl_output2_id](wl::TestWaylandServerThread* server) {
wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(wl_output2_id);
// Pretend that the output2 has scale factor equals to 2 now.
output2->SetScale(2);
output2->Flush();
});
// Entered outputs remain the same.
EXPECT_THAT(wayland_surface->entered_outputs(),
ElementsAre(output1_id, output2_id, output3_id));
// It must be the second entered output now.
EXPECT_EQ(2, connection_->wayland_output_manager()
->GetOutput(output2_id)
->scale_factor());
// The window_ must return the output with largest scale.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output2_id);
PostToServerAndWait([wl_output1_id](wl::TestWaylandServerThread* server) {
wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(wl_output1_id);
// Now, the output1 changes its scale factor to 2 as well.
output1->SetScale(2);
output1->Flush();
});
// It must be the very first output now.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
PostToServerAndWait([wl_output1_id](wl::TestWaylandServerThread* server) {
wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(wl_output1_id);
// Now, the output1 changes its scale factor back to 1.
output1->SetScale(1);
output1->Flush();
});
// It must be the very the second output now.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output2_id);
PostToServerAndWait([wl_output2_id](wl::TestWaylandServerThread* server) {
wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(wl_output2_id);
// All outputs have scale factor of 1. window_ prefers the output that
// it entered first again.
output2->SetScale(1);
output2->Flush();
});
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
}
TEST_P(WaylandWindowTest, GetChildrenPreferredOutput) {
VerifyAndClearExpectations();
// Buffer scale must be 1 when no output has been entered by the window.
EXPECT_EQ(1, window_->applied_state().window_scale);
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(10, 10, 10, 10),
&menu_window_delegate, window_->GetWidget());
menu_window->Show(false);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
// Update the first output.
wl::TestOutput* output1 = server->output();
output1->SetPhysicalAndLogicalBounds({1920, 1080});
output1->Flush();
// Create a 2nd output.
server->CreateAndInitializeOutput(
wl::TestOutputMetrics({1921, 0, 1920, 1080}));
});
WaitForAllDisplaysReady();
// Client side WaylandOutput ids.
ASSERT_EQ(2u, screen_->GetAllDisplays().size());
const uint32_t output1_id =
screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(0).id());
const uint32_t output2_id =
screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(1).id());
auto* output_manager = connection_->wayland_output_manager();
ASSERT_EQ(2u, output_manager->GetAllOutputs().size());
// Shared wl_output resource ids.
const uint32_t wl_output1_id = GetObjIdForOutput(output1_id);
const uint32_t wl_output2_id = GetObjIdForOutput(output2_id);
// Client side surfaces.
WaylandSurface* root_surface = window_->root_surface();
WaylandSurface* menu_surface = menu_window->root_surface();
const uint32_t menu_surface_id = menu_surface->get_surface_id();
// Neither surface should have entered any output.
EXPECT_THAT(root_surface->entered_outputs(), ElementsAre());
EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre());
// Send the toplevel window into output1.
PostToServerAndWait(
[id = surface_id_, wl_output1_id](wl::TestWaylandServerThread* server) {
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(id)->resource(),
server->GetObject<wl::TestOutput>(wl_output1_id)->resource());
});
// The toplevel window entered the output.
EXPECT_THAT(root_surface->entered_outputs(), ElementsAre(output1_id));
EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre());
// The menu also thinks it entered the same output.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
// Send the menu window into output2, while the toplevel is still on output1.
PostToServerAndWait(
[menu_surface_id, wl_output2_id](wl::TestWaylandServerThread* server) {
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(menu_surface_id)->resource(),
server->GetObject<wl::TestOutput>(wl_output2_id)->resource());
});
// The menu surface should be aware of the output that Wayland sent it.
EXPECT_THAT(root_surface->entered_outputs(), ElementsAre(output1_id));
EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre(output2_id));
// The menu still prefers output1.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
// Send the toplevel window into output2.
PostToServerAndWait(
[id = surface_id_, wl_output2_id](wl::TestWaylandServerThread* server) {
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(id)->resource(),
server->GetObject<wl::TestOutput>(wl_output2_id)->resource());
});
// The toplevel window has now entered 2 outputs, in chronological order.
EXPECT_THAT(root_surface->entered_outputs(),
ElementsAre(output1_id, output2_id));
EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre(output2_id));
// With the same scale factor, the toplevel window prefers the earlier output.
// The menu window always prefers whatever the parent prefers.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
PostToServerAndWait([wl_output2_id](wl::TestWaylandServerThread* server) {
wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(wl_output2_id);
// Now, the output2 changes its scale factor to 2.
output2->SetScale(2);
output2->Flush();
});
// The toplevel window prefers the output with higher scale factor.
// The menu window still prefers whatever the parent prefers.
EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output2_id);
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
}
// Tests that xdg_popup is configured with default anchor properties and bounds
// if delegate doesn't have anchor properties set.
TEST_P(WaylandWindowTest, PopupPassesDefaultAnchorInformation) {
PopupPosition menu_window_positioner, nested_menu_window_positioner;
menu_window_positioner = {gfx::Rect(439, 46, 1, 1), gfx::Size(287, 409),
XDG_POSITIONER_ANCHOR_TOP_LEFT,
XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y};
nested_menu_window_positioner = {gfx::Rect(285, 1, 1, 1), gfx::Size(305, 99),
XDG_POSITIONER_ANCHOR_TOP_LEFT,
XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y};
auto* toplevel_window = window_.get();
toplevel_window->SetBoundsInDIP(gfx::Rect(739, 574));
// Case 1: properties are not provided. In this case, bounds' origin must
// be used as anchor rect and anchor position, gravity and constraints should
// be normal.
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
EXPECT_CALL(menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
.WillOnce(Return(std::nullopt));
gfx::Rect menu_window_bounds(gfx::Point(439, 46),
menu_window_positioner.size);
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds, &menu_window_delegate,
toplevel_window->GetWidget());
EXPECT_TRUE(menu_window);
VerifyXdgPopupPosition(menu_window.get(), menu_window_positioner);
EXPECT_CALL(menu_window_delegate, OnBoundsChanged(_)).Times(0);
SendConfigureEventPopup(menu_window.get(), menu_window_bounds);
EXPECT_EQ(menu_window->GetBoundsInDIP(), menu_window_bounds);
// Case 2: the nested menu window is positioned normally.
MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
connection_.get());
gfx::Rect nested_menu_window_bounds(gfx::Point(724, 47),
nested_menu_window_positioner.size);
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, nested_menu_window_bounds,
&nested_menu_window_delegate, menu_window->GetWidget());
EXPECT_TRUE(nested_menu_window);
VerifyXdgPopupPosition(nested_menu_window.get(),
nested_menu_window_positioner);
}
// Tests that xdg_popup is configured with anchor properties received from
// delegate.
TEST_P(WaylandWindowTest, PopupPassesSetAnchorInformation) {
PopupPosition menu_window_positioner, nested_menu_window_positioner;
menu_window_positioner = {gfx::Rect(468, 46, 28, 28), gfx::Size(320, 404),
XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT,
XDG_POSITIONER_GRAVITY_BOTTOM_LEFT,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y};
nested_menu_window_positioner = {
gfx::Rect(4, 83, 312, 1), gfx::Size(480, 294),
XDG_POSITIONER_ANCHOR_TOP_RIGHT, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y |
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X};
auto* toplevel_window = window_.get();
toplevel_window->SetBoundsInDIP(gfx::Rect(508, 212));
MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
ui::OwnedWindowAnchor anchor = {
gfx::Rect(menu_window_positioner.anchor_rect),
OwnedWindowAnchorPosition::kBottomRight,
OwnedWindowAnchorGravity::kBottomLeft,
OwnedWindowConstraintAdjustment::kAdjustmentFlipY};
EXPECT_CALL(menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
.WillOnce(Return(anchor));
gfx::Rect menu_window_bounds(gfx::Point(176, 74),
menu_window_positioner.size);
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds, &menu_window_delegate,
toplevel_window->GetWidget());
EXPECT_TRUE(menu_window);
VerifyXdgPopupPosition(menu_window.get(), menu_window_positioner);
MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
connection_.get());
anchor = {{180, 157, 312, 1},
OwnedWindowAnchorPosition::kTopRight,
OwnedWindowAnchorGravity::kBottomRight,
OwnedWindowConstraintAdjustment::kAdjustmentFlipY |
OwnedWindowConstraintAdjustment::kAdjustmentFlipX};
EXPECT_CALL(nested_menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
.WillOnce(Return(anchor));
gfx::Rect nested_menu_window_bounds(gfx::Point(492, 157),
nested_menu_window_positioner.size);
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, nested_menu_window_bounds,
&nested_menu_window_delegate, menu_window->GetWidget());
EXPECT_TRUE(nested_menu_window);
VerifyXdgPopupPosition(nested_menu_window.get(),
nested_menu_window_positioner);
}
TEST_P(WaylandWindowTest, SetBoundsResizesEmptySizes) {
display::test::TestScreen test_screen_{/*create_display=*/true,
/*register_screen=*/true};
auto* toplevel_window = window_.get();
toplevel_window->SetBoundsInDIP(gfx::Rect(666, 666));
testing::NiceMock<MockWaylandPlatformWindowDelegate> popup_delegate(
connection_.get());
gfx::Rect menu_window_bounds(gfx::Point(0, 0), {0, 0});
std::unique_ptr<WaylandWindow> popup = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds, &popup_delegate,
toplevel_window->GetWidget());
EXPECT_TRUE(popup);
popup->SetBoundsInDIP({0, 0, 0, 0});
VerifyXdgPopupPosition(
popup.get(),
{gfx::Rect(0, 0, 1, 1), gfx::Size(1, 1), XDG_POSITIONER_ANCHOR_TOP_LEFT,
XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y});
}
TEST_P(WaylandWindowTest, SetOpaqueRegion) {
gfx::Rect new_bounds(500, 600);
SkIRect rect =
SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height());
auto state_array = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED});
SendConfigureEvent(surface_id_, new_bounds.size(), state_array);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait(
[id = surface_id_, new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
});
new_bounds.set_size(gfx::Size(1000, 534));
rect = SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height());
SendConfigureEvent(surface_id_, new_bounds.size(), state_array);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait(
[id = surface_id_, new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
});
}
TEST_P(WaylandWindowTest, OnCloseRequest) {
EXPECT_CALL(delegate_, OnCloseRequest());
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
xdg_toplevel_send_close(xdg_surface->xdg_toplevel()->resource());
});
}
TEST_P(WaylandWindowTest, WaylandPopupSimpleParent) {
VerifyAndClearExpectations();
// WaylandPopup must ignore the parent provided by aura and should always
// use focused window instead.
gfx::Rect wayland_popup_bounds(gfx::Point(15, 15), gfx::Size(10, 10));
std::unique_ptr<WaylandWindow> wayland_popup = CreateWaylandWindowWithParams(
PlatformWindowType::kTooltip, wayland_popup_bounds, &delegate_,
window_->GetWidget());
EXPECT_TRUE(wayland_popup);
wayland_popup->Show(false);
PostToServerAndWait(
[id = wayland_popup->root_surface()->get_surface_id(),
wayland_popup_bounds](wl::TestWaylandServerThread* server) {
auto* mock_surface_popup = server->GetObject<wl::MockSurface>(id);
auto* mock_xdg_popup = mock_surface_popup->xdg_surface()->xdg_popup();
EXPECT_EQ(mock_xdg_popup->anchor_rect().origin(),
wayland_popup_bounds.origin());
EXPECT_EQ(mock_surface_popup->opaque_region(),
gfx::Rect(wayland_popup_bounds.size()));
});
wayland_popup->Hide();
}
TEST_P(WaylandWindowTest, WaylandPopupNestedParent) {
VerifyAndClearExpectations();
gfx::Rect menu_window_bounds(gfx::Point(10, 10), gfx::Size(100, 100));
auto menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds, &delegate_,
window_->GetWidget());
EXPECT_TRUE(menu_window);
VerifyAndClearExpectations();
SetPointerFocusedWindow(menu_window.get());
std::vector<PlatformWindowType> window_types{PlatformWindowType::kMenu,
PlatformWindowType::kTooltip};
for (const auto& type : window_types) {
gfx::Rect nested_wayland_popup_bounds(gfx::Point(15, 15),
gfx::Size(10, 10));
auto nested_wayland_popup =
CreateWaylandWindowWithParams(type, nested_wayland_popup_bounds,
&delegate_, menu_window->GetWidget());
EXPECT_TRUE(nested_wayland_popup);
VerifyAndClearExpectations();
nested_wayland_popup->Show(false);
PostToServerAndWait(
[id = nested_wayland_popup->root_surface()->get_surface_id(),
nested_wayland_popup_bounds,
menu_window_bounds](wl::TestWaylandServerThread* server) {
auto* mock_surface_nested = server->GetObject<wl::MockSurface>(id);
auto* mock_xdg_popup_nested =
mock_surface_nested->xdg_surface()->xdg_popup();
auto new_origin = nested_wayland_popup_bounds.origin() -
menu_window_bounds.origin().OffsetFromOrigin();
EXPECT_EQ(mock_xdg_popup_nested->anchor_rect().origin(), new_origin);
EXPECT_EQ(mock_surface_nested->opaque_region(),
gfx::Rect(nested_wayland_popup_bounds.size()));
});
SetPointerFocusedWindow(nullptr);
nested_wayland_popup->Hide();
}
}
// Tests that size constraints returned by the `ui::PlatformWindowDelegate` are
// obeyed by the window when its bounds are set internally via its
// SetBoundsInDIP() implementation.
TEST_P(WaylandWindowTest, SizeConstraintsInternal) {
constexpr gfx::Size kMinSize{100, 100};
constexpr gfx::Size kMaxSize{300, 300};
window_->SetBoundsInDIP({0, 0, 200, 200});
gfx::Rect even_smaller_bounds(kMinSize);
even_smaller_bounds.Inset(10);
even_smaller_bounds.set_origin({0, 0});
EXPECT_CALL(delegate_, GetMinimumSizeForWindow()).WillOnce(Return(kMinSize));
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetBoundsInDIP(even_smaller_bounds);
VerifyAndClearExpectations();
gfx::Rect even_greater_bounds(kMaxSize);
even_greater_bounds.Outset(10);
even_greater_bounds.set_origin({0, 0});
EXPECT_CALL(delegate_, GetMaximumSizeForWindow()).WillOnce(Return(kMaxSize));
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->SetBoundsInDIP(even_greater_bounds);
}
// Tests that size constraints returned by the `ui::PlatformWindowDelegate` are
// obeyed by the window when its bounds are set externally via the configure
// event sent by the compositor.
TEST_P(WaylandWindowTest, SizeConstraintsExternal) {
constexpr gfx::Size kMinSize{100, 100};
constexpr gfx::Size kMaxSize{300, 300};
EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
.WillRepeatedly(Return(kMinSize));
EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
.WillRepeatedly(Return(kMaxSize));
window_->SetBoundsInDIP({0, 0, 200, 200});
auto state = InitializeWlArrayWithActivatedState();
gfx::Rect even_smaller_bounds(kMinSize);
even_smaller_bounds.Inset(10);
even_smaller_bounds.set_origin({0, 0});
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
SendConfigureEvent(surface_id_, even_smaller_bounds.size(), state);
VerifyAndClearExpectations();
EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
.WillRepeatedly(Return(kMinSize));
EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
.WillRepeatedly(Return(kMaxSize));
gfx::Rect even_greater_bounds(kMaxSize);
even_greater_bounds.Outset(10);
even_greater_bounds.set_origin({0, 0});
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
SendConfigureEvent(surface_id_, even_greater_bounds.size(), state);
}
TEST_P(WaylandWindowTest, OnSizeConstraintsChanged) {
const bool kBooleans[] = {false, true};
for (bool has_min_size : kBooleans) {
for (bool has_max_size : kBooleans) {
std::optional<gfx::Size> min_size =
has_min_size ? std::optional<gfx::Size>(gfx::Size(100, 200))
: std::nullopt;
std::optional<gfx::Size> max_size =
has_max_size ? std::optional<gfx::Size>(gfx::Size(300, 400))
: std::nullopt;
EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
.WillOnce(Return(min_size));
EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
.WillOnce(Return(max_size));
PostToServerAndWait([id = surface_id_, has_min_size,
has_max_size](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
auto* mock_xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*mock_xdg_surface->xdg_toplevel(), SetMinSize(100, 200))
.Times(has_min_size ? 1 : 0);
EXPECT_CALL(*mock_xdg_surface->xdg_toplevel(), SetMaxSize(300, 400))
.Times(has_max_size ? 1 : 0);
});
window_->SizeConstraintsChanged();
VerifyAndClearExpectations();
}
}
}
TEST_P(WaylandWindowTest, DestroysCreatesSurfaceOnHideShow) {
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto window = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
gfx::Rect(100, 100), &delegate);
ASSERT_TRUE(window);
const uint32_t surface_id = window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
});
window->Hide();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_FALSE(mock_surface->xdg_surface());
});
window->Show(false);
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
});
}
TEST_P(WaylandWindowTest, DestroysCreatesPopupsOnHideShow) {
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto window = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(50, 50), &delegate,
window_->GetWidget());
ASSERT_TRUE(window);
const uint32_t surface_id = window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_popup());
});
window->Hide();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_FALSE(mock_surface->xdg_surface());
});
window->Show(false);
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_popup());
});
}
TEST_P(WaylandWindowTest, ReattachesBackgroundOnShow) {
EXPECT_TRUE(connection_->buffer_manager_host());
auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
buffer_manager_gpu_->Initialize(std::move(interface_ptr), {},
/*supports_dma_buf=*/false,
/*supports_viewporter=*/true,
/*supports_acquire_fence=*/false,
/*supports_overlays=*/true,
/*supports_single_pixel_buffer=*/true);
// Setup wl_buffers.
constexpr uint32_t buffer_id1 = 1;
constexpr uint32_t buffer_id2 = 2;
gfx::Size buffer_size(1024, 768);
auto length = 1024 * 768 * 4;
buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
buffer_id1);
buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
buffer_id2);
base::RunLoop().RunUntilIdle();
// Create window.
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto window = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
gfx::Rect(100, 100), &delegate);
ASSERT_TRUE(window);
auto states = InitializeWlArrayWithActivatedState();
// Configure window to be ready to attach wl_buffers.
const uint32_t surface_id = window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
});
SendConfigureEvent(surface_id, {100, 100}, states);
// Commit a frame with only background.
std::vector<wl::WaylandOverlayConfig> overlays;
wl::WaylandOverlayConfig background;
background.z_order = INT32_MIN;
background.buffer_id = buffer_id1;
overlays.push_back(std::move(background));
buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 1u, gfx::FrameData(),
std::move(overlays));
// Let mojo messages from gpu to host go through.
base::RunLoop().RunUntilIdle();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
mock_surface->SendFrameCallback();
});
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
EXPECT_NE(mock_surface->attached_buffer(), nullptr);
});
window->Hide();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
mock_surface->SendFrameCallback();
});
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
// `WaylandToplevelWindow::Hide()` should have already released the buffer.
EXPECT_EQ(mock_surface->attached_buffer(), nullptr);
});
window->Show(false);
SendConfigureEvent(surface_id, {100, 100}, states);
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
// Expects to receive an attach request on next frame.
EXPECT_CALL(*mock_surface, Attach(_, _, _)).Times(1);
});
// Commit a frame with only the primary_plane.
overlays.clear();
wl::WaylandOverlayConfig primary;
primary.z_order = 0;
primary.buffer_id = buffer_id2;
overlays.push_back(std::move(primary));
buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 2u, gfx::FrameData(),
std::move(overlays));
// Let mojo messages from gpu to host go through.
base::RunLoop().RunUntilIdle();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
// WaylandWindow should automatically reattach the background.
EXPECT_NE(mock_surface->attached_buffer(), nullptr);
});
}
// Tests that if the window gets hidden and shown again, the title, app id and
// size constraints remain the same.
TEST_P(WaylandWindowTest, SetsPropertiesOnShow) {
constexpr char kAppId[] = "wayland_test";
const std::u16string kTitle(u"WaylandWindowTest");
PlatformWindowInitProperties properties;
properties.bounds = gfx::Rect(100, 100);
properties.type = PlatformWindowType::kWindow;
properties.wm_class_class = kAppId;
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto window =
delegate.CreateWaylandWindow(connection_.get(), std::move(properties));
ASSERT_TRUE(window);
window->Show(false);
const uint32_t surface_id = window->root_surface()->get_surface_id();
PostToServerAndWait([surface_id, app_id = window->GetWindowUniqueId()](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
// Only app id must be set now.
EXPECT_EQ(app_id, mock_xdg_toplevel->app_id());
EXPECT_TRUE(mock_xdg_toplevel->title().empty());
EXPECT_TRUE(mock_xdg_toplevel->min_size().IsEmpty());
EXPECT_TRUE(mock_xdg_toplevel->max_size().IsEmpty());
});
// Now, propagate size constraints and title.
std::optional<gfx::Size> min_size(gfx::Size(1, 1));
std::optional<gfx::Size> max_size(gfx::Size(100, 100));
EXPECT_CALL(delegate, GetMinimumSizeForWindow())
.WillRepeatedly(Return(min_size));
EXPECT_CALL(delegate, GetMaximumSizeForWindow())
.WillRepeatedly(Return(max_size));
PostToServerAndWait([surface_id, min_size, max_size,
kTitle](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
EXPECT_CALL(*mock_xdg_toplevel, SetMinSize(min_size.value().width(),
min_size.value().height()));
EXPECT_CALL(*mock_xdg_toplevel, SetMaxSize(max_size.value().width(),
max_size.value().height()));
EXPECT_CALL(*mock_xdg_toplevel, SetTitle(base::UTF16ToUTF8(kTitle)));
});
window->SetTitle(kTitle);
window->SizeConstraintsChanged();
VerifyAndClearExpectations();
window->Hide();
WaylandTestBase::SyncDisplay();
window->Show(false);
PostToServerAndWait([surface_id, min_size, max_size, kTitle,
app_id = window->GetWindowUniqueId()](
wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
// We can't mock all those methods above as long as the xdg_toplevel is
// created and destroyed on each show and hide call. However, it is the same
// WaylandToplevelWindow object that cached the values we set and must
// restore them on Show().
EXPECT_EQ(mock_xdg_toplevel->min_size(), min_size.value());
EXPECT_EQ(mock_xdg_toplevel->max_size(), max_size.value());
EXPECT_EQ(app_id, mock_xdg_toplevel->app_id());
EXPECT_EQ(mock_xdg_toplevel->title(), base::UTF16ToUTF8(kTitle));
});
}
// Tests that a popup window is created using the serial of button press
// events as required by the Wayland protocol spec.
TEST_P(WaylandWindowTest, CreatesPopupOnButtonPressSerial) {
for (bool use_explicit_grab : {false, true}) {
base::test::ScopedCommandLine command_line_;
if (use_explicit_grab) {
command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kUseWaylandExplicitGrab);
}
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(
server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD);
});
constexpr uint32_t keyboard_enter_serial = 1;
constexpr uint32_t pointer_enter_serial = 2;
constexpr uint32_t button_press_serial = 3;
constexpr uint32_t button_release_serial = 4;
PostToServerAndWait([id =
surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface =
server->GetObject<wl::MockSurface>(id);
wl::ScopedWlArray empty({});
wl_keyboard_send_enter(server->seat()->keyboard()->resource(),
keyboard_enter_serial,
toplevel_surface->resource(), empty.get());
wl_pointer_send_enter(server->seat()->pointer()->resource(),
pointer_enter_serial, toplevel_surface->resource(),
wl_fixed_from_int(0), wl_fixed_from_int(0));
// Send two events - button down and button up.
wl_pointer_send_button(server->seat()->pointer()->resource(),
button_press_serial, 1002, BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
wl_pointer_send_button(server->seat()->pointer()->resource(),
button_release_serial, 1004, BTN_LEFT,
WL_POINTER_BUTTON_STATE_RELEASED);
});
// Create a popup window and verify the client used correct serial.
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(50, 50), &delegate,
window_->GetWidget());
ASSERT_TRUE(popup);
const uint32_t surface_id = popup->root_surface()->get_surface_id();
// Unfortunately, everything has to be captured as |use_explicit_grab| may
// not be used and |maybe_unused| doesn't work with lambda captures.
PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(test_popup);
if (use_explicit_grab) {
EXPECT_NE(test_popup->grab_serial(), button_release_serial);
EXPECT_EQ(test_popup->grab_serial(), button_press_serial);
} else {
EXPECT_EQ(test_popup->grab_serial(), 0U);
}
});
}
}
// Tests that a popup window is created using the serial of touch down events
// as required by the Wayland protocol spec.
TEST_P(WaylandWindowTest, CreatesPopupOnTouchDownSerial) {
for (bool use_explicit_grab : {false, true}) {
base::test::ScopedCommandLine command_line_;
if (use_explicit_grab) {
command_line_.GetProcessCommandLine()->AppendSwitch(
switches::kUseWaylandExplicitGrab);
}
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(
server->seat()->resource(),
WL_SEAT_CAPABILITY_TOUCH | WL_SEAT_CAPABILITY_KEYBOARD);
});
constexpr uint32_t enter_serial = 1;
constexpr uint32_t touch_down_serial = 2;
constexpr uint32_t touch_up_serial = 3;
PostToServerAndWait([id =
surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface =
server->GetObject<wl::MockSurface>(id);
wl::ScopedWlArray empty({});
wl_keyboard_send_enter(server->seat()->keyboard()->resource(),
enter_serial, toplevel_surface->resource(),
empty.get());
// Send two events - touch down and touch up.
wl_touch_send_down(server->seat()->touch()->resource(), touch_down_serial,
0, toplevel_surface->resource(), 0 /* id */,
wl_fixed_from_int(50), wl_fixed_from_int(100));
wl_touch_send_frame(server->seat()->touch()->resource());
wl_touch_send_up(server->seat()->touch()->resource(), touch_up_serial,
1000, 0 /* id */);
wl_touch_send_frame(server->seat()->touch()->resource());
});
// Create a popup window and verify the client used correct serial.
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(50, 50), &delegate,
window_->GetWidget());
ASSERT_TRUE(popup);
const uint32_t surface_id = popup->root_surface()->get_surface_id();
// Unfortunately, everything has to be captured as |use_explicit_grab| may
// not be used and |maybe_unused| doesn't work with lambda captures.
PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(test_popup);
// Unless the use-wayland-explicit-grab switch is set, touch events
// are the exception, i.e: the serial sent before the "up" event
// (latest) cannot be used, otherwise, some compositors may dismiss
// popups.
if (!use_explicit_grab) {
EXPECT_EQ(test_popup->grab_serial(), 0U);
}
});
popup->Hide();
PostToServerAndWait([id =
surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface =
server->GetObject<wl::MockSurface>(id);
// Send a single down event now.
wl_touch_send_down(server->seat()->touch()->resource(), touch_down_serial,
0, toplevel_surface->resource(), 0 /* id */,
wl_fixed_from_int(50), wl_fixed_from_int(100));
wl_touch_send_frame(server->seat()->touch()->resource());
});
popup->Show(false);
// Unfortunately, everything has to be captured as |use_explicit_grab| may
// not be used and |maybe_unused| doesn't work with lambda captures.
PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(test_popup);
uint32_t expected_serial = touch_down_serial;
auto env = base::Environment::Create();
if (base::nix::GetDesktopEnvironment(env.get()) ==
base::nix::DESKTOP_ENVIRONMENT_GNOME) {
// We do not grab with touch events on gnome shell.
expected_serial = 0u;
}
if (use_explicit_grab) {
EXPECT_EQ(test_popup->grab_serial(), expected_serial);
} else {
EXPECT_EQ(test_popup->grab_serial(), 0U);
}
});
}
}
// Tests nested menu windows get the topmost window in the stack of windows
// within the same family/tree.
TEST_P(WaylandWindowTest, NestedPopupWindowsGetCorrectParent) {
VerifyAndClearExpectations();
gfx::Rect menu_window_bounds(gfx::Rect(10, 20, 20, 20));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds, &delegate_,
window_->GetWidget());
EXPECT_TRUE(menu_window);
EXPECT_TRUE(menu_window->parent_window() == window_.get());
gfx::Rect menu_window_bounds2(gfx::Rect(20, 40, 30, 20));
std::unique_ptr<WaylandWindow> menu_window2 = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds2, &delegate_,
menu_window->GetWidget());
EXPECT_TRUE(menu_window2);
EXPECT_TRUE(menu_window2->parent_window() == menu_window.get());
gfx::Rect menu_window_bounds3(gfx::Rect(30, 40, 30, 20));
std::unique_ptr<WaylandWindow> menu_window3 = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds3, &delegate_,
menu_window2->GetWidget());
EXPECT_TRUE(menu_window3);
EXPECT_TRUE(menu_window3->parent_window() == menu_window2.get());
gfx::Rect menu_window_bounds4(gfx::Rect(40, 40, 30, 20));
std::unique_ptr<WaylandWindow> menu_window4 = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds4, &delegate_,
menu_window3->GetWidget());
EXPECT_TRUE(menu_window4);
EXPECT_TRUE(menu_window4->parent_window() == menu_window3.get());
}
TEST_P(WaylandWindowTest, DoesNotGrabPopupIfNoSeat) {
// Create a popup window and verify the grab serial is not set.
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(50, 50), &delegate,
window_->GetWidget());
ASSERT_TRUE(popup);
PostToServerAndWait([surface_id = popup->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(test_popup);
EXPECT_EQ(test_popup->grab_serial(), 0u);
});
}
// Regression test for https://crbug.com/1247799.
TEST_P(WaylandWindowTest, DoesNotGrabPopupUnlessParentHasGrab) {
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
SetPointerFocusedWindow(window_.get());
// Emulate a root menu creation with no serial available and ensure
// ozone/wayland does not attempt to grab it.
connection_->serial_tracker().ClearForTesting();
MockWaylandPlatformWindowDelegate delegate(connection_.get());
std::unique_ptr<WaylandWindow> root_menu;
root_menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(50, 50), &delegate,
window_->GetWidget());
VerifyAndClearExpectations();
Mock::VerifyAndClearExpectations(&delegate);
ASSERT_TRUE(root_menu);
PostToServerAndWait(
[surface_id = root_menu->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* server_root_menu = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(server_root_menu);
EXPECT_EQ(server_root_menu->grab_serial(), 0u);
});
// Emulate a nested menu creation triggered by a mouse button event and ensure
// ozone/wayland does not attempt to grab it, as its parent also has not grab.
EXPECT_CALL(delegate, DispatchEvent(_)).Times(2);
PostToServerAndWait(
[surface_id = root_menu->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* server_root_menu_surface =
server->GetObject<wl::MockSurface>(surface_id);
ASSERT_TRUE(server_root_menu_surface);
auto* pointer_resource = server->seat()->pointer()->resource();
wl_pointer_send_enter(pointer_resource, 3u /*serial*/,
server_root_menu_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer_resource);
wl_pointer_send_button(pointer_resource, 4u /*serial*/, 1, BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
wl_pointer_send_frame(pointer_resource);
});
Mock::VerifyAndClearExpectations(&delegate);
MockWaylandPlatformWindowDelegate delegate_2(connection_.get());
std::unique_ptr<WaylandWindow> child_menu;
child_menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
gfx::Rect(10, 10), &delegate_2,
root_menu->GetWidget());
VerifyAndClearExpectations();
Mock::VerifyAndClearExpectations(&delegate_2);
ASSERT_TRUE(child_menu);
PostToServerAndWait(
[surface_id = child_menu->root_surface()->get_surface_id(),
root_menu_id = root_menu->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* server_child_menu = GetTestXdgPopupByWindow(server, surface_id);
ASSERT_TRUE(server_child_menu);
EXPECT_EQ(server_child_menu->grab_serial(), 0u);
auto* pointer_resource = server->seat()->pointer()->resource();
auto* server_root_menu_surface =
server->GetObject<wl::MockSurface>(root_menu_id);
wl_pointer_send_leave(pointer_resource, 5u /*serial*/,
server_root_menu_surface->resource());
wl_pointer_send_frame(pointer_resource);
});
}
TEST_P(WaylandWindowTest, InitialBounds) {
testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate_2(
connection_.get());
auto toplevel = CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, gfx::Rect(10, 10, 200, 200), &delegate_2);
{
WaylandWindow::WindowStates window_states;
window_states.is_maximized = false;
window_states.is_fullscreen = false;
window_states.is_activated = true;
toplevel->HandleToplevelConfigureWithOrigin(0, 0, 0, 0, window_states);
}
toplevel->HandleSurfaceConfigure(2);
EXPECT_EQ(gfx::Rect(10, 10, 200, 200), toplevel->GetBoundsInDIP());
}
namespace {
class WaylandSubsurfaceTest : public WaylandWindowTest {
public:
WaylandSubsurfaceTest() = default;
~WaylandSubsurfaceTest() override = default;
protected:
void OneWaylandSubsurfaceTestHelper(
const gfx::RectF& subsurface_bounds,
const gfx::RectF& expected_subsurface_bounds) {
VerifyAndClearExpectations();
std::unique_ptr<WaylandWindow> window = CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, gfx::Rect(640, 480), &delegate_);
EXPECT_TRUE(window);
bool result = window->RequestSubsurface();
EXPECT_TRUE(result);
connection_->Flush();
WaylandSubsurface* wayland_subsurface =
window->wayland_subsurfaces().begin()->get();
PostToServerAndWait(
[subsurface_id =
wayland_subsurface->wayland_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* mock_surface_subsurface =
server->GetObject<wl::MockSurface>(subsurface_id);
EXPECT_TRUE(mock_surface_subsurface);
});
wayland_subsurface->ConfigureAndShowSurface(
subsurface_bounds, gfx::RectF(0, 0, 640, 480) /*parent_bounds_px*/,
1.f /*buffer_scale*/, nullptr, nullptr);
connection_->Flush();
PostToServerAndWait(
[surface_id = window->root_surface()->get_surface_id(),
subsurface_id =
wayland_subsurface->wayland_surface()->get_surface_id(),
expected_subsurface_bounds](wl::TestWaylandServerThread* server) {
auto* mock_surface_root_window =
server->GetObject<wl::MockSurface>(surface_id);
auto* mock_surface_subsurface =
server->GetObject<wl::MockSurface>(subsurface_id);
EXPECT_TRUE(mock_surface_subsurface);
auto* test_subsurface = mock_surface_subsurface->sub_surface();
EXPECT_TRUE(test_subsurface);
auto* parent_resource = mock_surface_root_window->resource();
EXPECT_EQ(parent_resource, test_subsurface->parent_resource());
// The conversion from double to fixed and back is necessary because
// it
// happens during the roundtrip, and it creates significant error.
gfx::PointF expected_position(wl_fixed_to_double(wl_fixed_from_double(
expected_subsurface_bounds.x())),
wl_fixed_to_double(wl_fixed_from_double(
expected_subsurface_bounds.y())));
EXPECT_EQ(test_subsurface->position(), expected_position);
EXPECT_TRUE(test_subsurface->sync());
});
}
std::vector<WaylandSubsurface*> RequestWaylandSubsurface(uint32_t n) {
VerifyAndClearExpectations();
std::vector<WaylandSubsurface*> res =
std::vector<WaylandSubsurface*>{window_->primary_subsurface()};
for (uint32_t i = 0; i < n - 1; ++i) {
window_->RequestSubsurface();
}
for (auto& subsurface : window_->wayland_subsurfaces()) {
res.push_back(subsurface.get());
}
return res;
}
};
} // namespace
// Tests integer and non integer size/position support. Ozone/Wayland is
// expected to ceil the bounds.
TEST_P(WaylandSubsurfaceTest, OneWaylandSubsurfaceBoundsCeil) {
constexpr std::array<std::array<gfx::RectF, 2>, 4> test_data = {
{{gfx::RectF({15.12, 15.912}, {10.351, 10.742}),
gfx::RectF({16, 16}, {11, 11})},
{gfx::RectF({7.041, 8.583}, {13.452, 20.231}),
gfx::RectF({8, 9}, {14, 21})},
{gfx::RectF({15, 15}, {10, 10}), gfx::RectF({15, 15}, {10, 10})},
{gfx::RectF({7, 8}, {16, 18}), gfx::RectF({7, 8}, {16, 18})}}};
for (const auto& item : test_data) {
OneWaylandSubsurfaceTestHelper(item[0] /* subsurface_bounds */,
item[1] /* expected_subsurface_bounds */);
};
}
TEST_P(WaylandSubsurfaceTest, NoDuplicateSubsurfaceRequests) {
if (!connection_->ShouldUseOverlayDelegation()) {
GTEST_SKIP();
}
auto subsurfaces = RequestWaylandSubsurface(3);
for (auto* subsurface : subsurfaces) {
subsurface->ConfigureAndShowSurface(gfx::RectF(1.f, 2.f, 10.f, 20.f),
gfx::RectF(0.f, 0.f, 800.f, 600.f), 1.f,
nullptr, nullptr);
}
connection_->Flush();
PostToServerAndWait(
[subsurface_id1 = subsurfaces[0]->wayland_surface()->get_surface_id(),
subsurface_id2 = subsurfaces[1]->wayland_surface()->get_surface_id(),
subsurface_id3 = subsurfaces[2]->wayland_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
// From top to bottom: subsurfaces[2], subsurfaces[1], subsurfaces[0].
std::array<wl::TestSubSurface*, 3> test_subs = {
server->GetObject<wl::MockSurface>(subsurface_id1)->sub_surface(),
server->GetObject<wl::MockSurface>(subsurface_id2)->sub_surface(),
server->GetObject<wl::MockSurface>(subsurface_id3)->sub_surface(),
};
EXPECT_CALL(*test_subs[0], PlaceAbove(_)).Times(1);
EXPECT_CALL(*test_subs[0], PlaceBelow(_)).Times(0);
EXPECT_CALL(*test_subs[0], SetPosition(_, _)).Times(1);
EXPECT_CALL(*test_subs[1], PlaceAbove(_)).Times(0);
EXPECT_CALL(*test_subs[1], PlaceBelow(_)).Times(0);
EXPECT_CALL(*test_subs[1], SetPosition(_, _)).Times(0);
EXPECT_CALL(*test_subs[2], PlaceAbove(_)).Times(0);
EXPECT_CALL(*test_subs[2], PlaceBelow(_)).Times(0);
EXPECT_CALL(*test_subs[2], SetPosition(_, _)).Times(0);
});
// Stack subsurfaces[0] to be from bottom to top, and change its position.
subsurfaces[0]->ConfigureAndShowSurface(gfx::RectF(0.f, 0.f, 10.f, 20.f),
gfx::RectF(0.f, 0.f, 800.f, 600.f),
1.f, subsurfaces[2], nullptr);
subsurfaces[1]->ConfigureAndShowSurface(gfx::RectF(1.f, 2.f, 10.f, 20.f),
gfx::RectF(0.f, 0.f, 800.f, 600.f),
1.f, nullptr, subsurfaces[2]);
subsurfaces[2]->ConfigureAndShowSurface(gfx::RectF(1.f, 2.f, 10.f, 20.f),
gfx::RectF(0.f, 0.f, 800.f, 600.f),
1.f, nullptr, subsurfaces[0]);
connection_->Flush();
VerifyAndClearExpectations();
}
TEST_P(WaylandWindowTest, NoDuplicateViewporterRequests) {
EXPECT_TRUE(connection_->buffer_manager_host());
auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
buffer_manager_gpu_->Initialize(std::move(interface_ptr), {},
/*supports_dma_buf=*/false,
/*supports_viewporter=*/true,
/*supports_acquire_fence=*/false,
/*supports_overlays=*/true,
/*supports_single_pixel_buffer=*/true);
// Setup wl_buffers.
constexpr uint32_t buffer_id = 1;
gfx::Size buffer_size(1024, 768);
auto length = 1024 * 768 * 4;
buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
buffer_id);
base::RunLoop().RunUntilIdle();
auto* surface = window_->root_surface();
PostToServerAndWait([surface_id = surface->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* test_viewport =
server->GetObject<wl::MockSurface>(surface_id)->viewport();
// Set viewport src and dst.
EXPECT_CALL(*test_viewport, SetSource(512, 384, 512, 384)).Times(1);
EXPECT_CALL(*test_viewport, SetDestination(800, 600)).Times(1);
});
surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
surface, buffer_id));
surface->set_buffer_crop({0.5, 0.5, 0.5, 0.5});
surface->set_viewport_destination({800, 600});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
VerifyAndClearExpectations();
PostToServerAndWait([surface_id = surface->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* test_viewport =
server->GetObject<wl::MockSurface>(surface_id)->viewport();
Mock::VerifyAndClearExpectations(test_viewport);
// Duplicate viewport requests are not sent.
EXPECT_CALL(*test_viewport, SetSource(_, _, _, _)).Times(0);
EXPECT_CALL(*test_viewport, SetDestination(_, _)).Times(0);
});
surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
surface, buffer_id));
surface->set_buffer_crop({0.5, 0.5, 0.5, 0.5});
surface->set_viewport_destination({800, 600});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
VerifyAndClearExpectations();
PostToServerAndWait([surface_id = surface->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* test_viewport =
server->GetObject<wl::MockSurface>(surface_id)->viewport();
Mock::VerifyAndClearExpectations(test_viewport);
// Unset viewport src and dst.
EXPECT_CALL(*test_viewport, SetSource(-1, -1, -1, -1)).Times(1);
EXPECT_CALL(*test_viewport, SetDestination(-1, -1)).Times(1);
});
surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
surface, buffer_id));
surface->set_buffer_crop({0., 0., 1., 1.});
surface->set_viewport_destination({1024, 768});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
VerifyAndClearExpectations();
PostToServerAndWait([surface_id = surface->get_surface_id()](
wl::TestWaylandServerThread* server) {
auto* test_viewport =
server->GetObject<wl::MockSurface>(surface_id)->viewport();
Mock::VerifyAndClearExpectations(test_viewport);
// Duplicate viewport requests are not sent.
EXPECT_CALL(*test_viewport, SetSource(_, _, _, _)).Times(0);
EXPECT_CALL(*test_viewport, SetDestination(_, _)).Times(0);
});
surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
surface, buffer_id));
surface->set_buffer_crop({0., 0., 1., 1.});
surface->set_viewport_destination({1024, 768});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
VerifyAndClearExpectations();
}
// Tests that WaylandPopups can be repositioned.
TEST_P(WaylandWindowTest, RepositionPopups) {
VerifyAndClearExpectations();
gfx::Rect menu_window_bounds(gfx::Rect(6, 20, 8, 20));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_bounds, &delegate_,
window_->GetWidget());
EXPECT_TRUE(menu_window);
EXPECT_TRUE(menu_window->IsVisible());
PostToServerAndWait(
[id = menu_window->root_surface()->get_surface_id(),
menu_window_bounds](wl::TestWaylandServerThread* server) {
auto* mock_surface_popup = server->GetObject<wl::MockSurface>(id);
auto* mock_xdg_popup = mock_surface_popup->xdg_surface()->xdg_popup();
EXPECT_EQ(mock_xdg_popup->anchor_rect().origin(),
menu_window_bounds.origin());
EXPECT_EQ(mock_xdg_popup->size(), menu_window_bounds.size());
EXPECT_EQ(mock_surface_popup->opaque_region(),
gfx::Rect(menu_window_bounds.size()));
});
VerifyAndClearExpectations();
const gfx::Rect damage_rect{menu_window_bounds.width(),
menu_window_bounds.height()};
EXPECT_CALL(delegate_, OnDamageRect(Eq(damage_rect))).Times(1);
menu_window_bounds.set_origin({10, 10});
menu_window->SetBoundsInDIP(menu_window_bounds);
PostToServerAndWait(
[id = menu_window->root_surface()->get_surface_id(),
menu_window_bounds](wl::TestWaylandServerThread* server) {
// Xdg objects can be recreated depending on the version of the xdg
// shell.
auto* mock_surface_popup = server->GetObject<wl::MockSurface>(id);
auto* mock_xdg_popup = mock_surface_popup->xdg_surface()->xdg_popup();
EXPECT_EQ(mock_xdg_popup->anchor_rect().origin(),
menu_window_bounds.origin());
EXPECT_EQ(mock_xdg_popup->size(), menu_window_bounds.size());
EXPECT_EQ(mock_surface_popup->opaque_region(),
gfx::Rect(menu_window_bounds.size()));
});
// This will send a configure event for the xdg_surface that backs the
// xdg_popup. Size and state are not used there.
SendConfigureEvent(menu_window->root_surface()->get_surface_id(), {0, 0},
wl::ScopedWlArray({}));
VerifyAndClearExpectations();
}
// If buffers are not attached (aka WaylandBufferManagerHost is not used for
// buffer management), WaylandSurface::Commit mustn't result in creation of
// surface sync.
TEST_P(WaylandWindowTest, DoesNotCreateSurfaceSyncOnCommitWithoutBuffers) {
EXPECT_THAT(window_->root_surface()->surface_sync_, nullptr);
window_->root_surface()->Commit();
EXPECT_THAT(window_->root_surface()->surface_sync_, nullptr);
}
TEST_P(WaylandWindowTest, StartWithMinimized) {
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
SendConfigureEvent(surface_id_, {0, 0},
InitializeWlArrayWithActivatedState());
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Minimize();
VerifyAndClearExpectations();
// The state of the window has to be already minimized.
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
// We don't receive any state change if that does not differ from the last
// state.
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
// It must be still the same minimized state.
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
// The window geometry has to be set to the current bounds of the window for
// minimized state.
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(id);
auto* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
});
// Send one additional empty configuration event for minimized state.
// (which means the surface is not maximized, fullscreen or activated)
SendConfigureEvent(surface_id_, {0, 0}, wl::ScopedWlArray({}));
}
class BlockableWaylandToplevelWindow : public WaylandToplevelWindow {
public:
BlockableWaylandToplevelWindow(MockWaylandPlatformWindowDelegate* delegate,
WaylandConnection* connection)
: WaylandToplevelWindow(delegate, connection) {}
static std::unique_ptr<BlockableWaylandToplevelWindow> Create(
const gfx::Rect& bounds,
WaylandConnection* connection,
MockWaylandPlatformWindowDelegate* delegate) {
auto window =
std::make_unique<BlockableWaylandToplevelWindow>(delegate, connection);
PlatformWindowInitProperties properties;
properties.bounds = bounds;
properties.type = PlatformWindowType::kWindow;
properties.parent_widget = gfx::kNullAcceleratedWidget;
window->Initialize(std::move(properties));
window->Show(false);
return window;
}
// WaylandToplevelWindow overrides:
uint32_t DispatchEvent(const PlatformEvent& platform_event) override {
ui::Event* event(platform_event);
if (event->type() == EventType::kTouchReleased && !blocked_) {
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
blocked_ = true;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(async_task_), run_loop.QuitClosure()));
run_loop.Run();
blocked_ = false;
}
return WaylandToplevelWindow::DispatchEvent(platform_event);
}
void SetAsyncTask(
base::RepeatingCallback<void(base::OnceClosure)> async_task) {
async_task_ = std::move(async_task);
}
private:
bool blocked_ = false;
base::RepeatingCallback<void(base::OnceClosure)> async_task_;
};
// This test ensures that Ozone/Wayland does not crash while handling a
// sequence of two or more touch down/up actions, where the first one blocks
// unfinished before the second pair comes in.
//
// This mimics the behavior of a modal dialog that comes up as a result of
// the first touch down/up action, and blocks the original flow, before it gets
// handled completely.
TEST_P(WaylandWindowTest, BlockingTouchDownUp_NoCrash) {
window_.reset();
MockWaylandPlatformWindowDelegate delegate(connection_.get());
auto window = BlockableWaylandToplevelWindow::Create(
gfx::Rect(800, 600), connection_.get(), &delegate);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(
server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH);
});
ASSERT_TRUE(connection_->seat()->pointer());
ASSERT_TRUE(connection_->seat()->touch());
window->set_touch_focus(true);
// Test that CanDispatchEvent is set correctly.
VerifyCanDispatchTouchEvents({window.get()}, {});
// Steps to be executed after the handling of the first touch down/up
// pair blocks.
auto async_task = base::BindLambdaForTesting([&](base::OnceClosure closure) {
PostToServerAndWait([id = window->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface =
server->GetObject<wl::MockSurface>(id);
wl_touch_send_down(server->seat()->touch()->resource(),
server->GetNextSerial(), 0,
toplevel_surface->resource(), 0 /* id */,
wl_fixed_from_int(100), wl_fixed_from_int(100));
wl_touch_send_up(server->seat()->touch()->resource(),
server->GetNextSerial(), 2000, 0 /* id */);
wl_touch_send_frame(server->seat()->touch()->resource());
});
std::move(closure).Run();
});
window->SetAsyncTask(std::move(async_task));
PostToServerAndWait([id = window->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
// Start executing the first touch down/up pair.
wl_touch_send_down(server->seat()->touch()->resource(),
server->GetNextSerial(), 0, toplevel_surface->resource(),
0 /* id */, wl_fixed_from_int(50),
wl_fixed_from_int(50));
wl_touch_send_up(server->seat()->touch()->resource(),
server->GetNextSerial(), 1000, 0 /* id */);
wl_touch_send_frame(server->seat()->touch()->resource());
});
}
// Make sure that changing focus during dispatch will not re-dispatch the event
// to the newly focused window. (crbug.com/1339082);
// Flaky on device/VM: https://crbug.com/1348046
TEST_P(WaylandWindowTest, ChangeFocusDuringDispatch) {
MockPlatformWindowDelegate other_delegate;
gfx::AcceleratedWidget other_widget = gfx::kNullAcceleratedWidget;
EXPECT_CALL(other_delegate, OnAcceleratedWidgetAvailable(_))
.WillOnce(SaveArg<0>(&other_widget));
PlatformWindowInitProperties properties;
properties.bounds = gfx::Rect(10, 10);
properties.type = PlatformWindowType::kWindow;
auto other_window = WaylandWindow::Create(&other_delegate, connection_.get(),
std::move(properties));
ASSERT_NE(other_widget, gfx::kNullAcceleratedWidget);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
int count = 0;
EXPECT_CALL(other_delegate, DispatchEvent(_)).Times(1);
EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) {
count++;
if (event->type() == ui::EventType::kMousePressed) {
PostToServerAndWait(
[id = surface_id_,
other_id = other_window->root_surface()->get_surface_id()](
wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
wl::MockSurface* other_surface =
server->GetObject<wl::MockSurface>(other_id);
ASSERT_TRUE(other_surface);
auto* pointer = server->seat()->pointer();
// Leaving will trigger a synthesized release event on focus change.
wl_pointer_send_leave(pointer->resource(), 3, surface->resource());
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_enter(pointer->resource(), 4,
other_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
});
}
});
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
auto* pointer = server->seat()->pointer();
wl_pointer_send_enter(pointer->resource(), 1, surface->resource(), 0, 0);
// The Enter event is coupled with the frame event.
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_button(pointer->resource(), 2, 1004, BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
wl_pointer_send_frame(pointer->resource());
});
EXPECT_EQ(count, 4);
}
TEST_P(WaylandWindowTest, WindowMovedResized) {
const gfx::Rect initial_bounds = window_->GetBoundsInDIP();
gfx::Rect new_bounds(initial_bounds);
new_bounds.set_x(new_bounds.origin().x() + 10);
new_bounds.set_y(new_bounds.origin().y() + 10);
// Configure is not necessary to just move.
EXPECT_CALL(delegate_, OnBoundsChanged(BoundsChange(true)));
PostToServerAndWait([id = surface_id_,
new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
auto* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(0);
});
window_->SetBoundsInDIP(new_bounds);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Resize and move.
new_bounds.Inset(5);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange(true)))).Times(1);
PostToServerAndWait([id = surface_id_,
new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
auto* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(0);
});
window_->SetBoundsInDIP(new_bounds);
VerifyAndClearExpectations();
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange(true)))).Times(1);
PostToServerAndWait([id = surface_id_,
new_bounds](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
auto* xdg_surface = surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(1);
});
wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(surface_id_, new_bounds.size(), states);
AdvanceFrameToCurrent(window_.get(), delegate_);
}
// Make sure that creating a window with DIP bounds creates a window with
// the same DIP bounds with various fractional scales.
TEST_P(WaylandWindowTest, NoRoundingErrorInDIP) {
VerifyAndClearExpectations();
auto* primary_output =
connection_->wayland_output_manager()->GetPrimaryOutput();
constexpr float kScales[] = {display::kDsf_1_777, display::kDsf_2_252,
display::kDsf_2_666, display::kDsf_1_8};
for (float scale : kScales) {
primary_output->SetScaleFactorForTesting(scale);
// Update to delegate to use the correct scale;
window_->UpdateWindowScale(true);
testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate(
connection_.get());
std::unique_ptr<WaylandWindow> wayland_window =
CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
gfx::Rect(20, 0, 100, 100), &delegate);
for (int i = 100; i < 3000; i++) {
const gfx::Rect kBoundsDip{20, 0, i, 3000 - i};
const gfx::Rect bounds_in_px =
delegate_.ConvertRectToPixels(gfx::Rect(kBoundsDip.size()));
wayland_window->SetBoundsInDIP(kBoundsDip);
AdvanceFrameToCurrent(wayland_window.get(), delegate);
EXPECT_EQ(bounds_in_px.size(), wayland_window->applied_state().size_px);
EXPECT_EQ(kBoundsDip, wayland_window->GetBoundsInDIP());
}
}
VerifyAndClearExpectations();
}
// Make sure that the window scale change is applied on the latest
// in_flight_requests.
TEST_P(WaylandWindowTest, ScaleChangeWhenStateRequestThrottoled) {
VerifyAndClearExpectations();
gfx::Rect bounds_dip;
auto* toplevel = static_cast<WaylandToplevelWindow*>(window_.get());
for (int i = 300; i <= 600; i++) {
bounds_dip = {0, 0, i, 900 - i};
toplevel->HandleToplevelConfigure(bounds_dip.width(), bounds_dip.height(),
{});
toplevel->HandleSurfaceConfigure(i);
}
// latest bounds_dip is throttled, and not applied, scale factor is 1.
EXPECT_NE(bounds_dip.size(), toplevel->applied_state().bounds_dip.size());
EXPECT_EQ(bounds_dip.size(),
delegate_.ConvertRectToPixels(bounds_dip).size());
// Update to delegate to use the correct scale;
constexpr float kScale = display::kDsf_1_777;
auto* primary_output =
connection_->wayland_output_manager()->GetPrimaryOutput();
primary_output->SetScaleFactorForTesting(kScale);
toplevel->UpdateWindowScale(true);
AdvanceFrameToCurrent(window_.get(), delegate_);
// bounds_dip advances to be applied, and scaled correctly.
EXPECT_EQ(bounds_dip.size(), toplevel->applied_state().bounds_dip.size());
EXPECT_NE(bounds_dip.size(),
delegate_.ConvertRectToPixels(bounds_dip).size());
EXPECT_EQ(window_->applied_state().size_px,
delegate_.ConvertRectToPixels(bounds_dip).size());
VerifyAndClearExpectations();
}
// Tests that a re-entrant state update is handled serially by `WaylandWindow`
// and does not crash.
TEST_P(WaylandWindowTest, ReentrantApplyStateWorks) {
constexpr gfx::Rect kBounds1{123, 234};
constexpr gfx::Rect kBounds2{234, 345};
constexpr gfx::Rect kBounds3{345, 456};
PostToServerAndWait([id = surface_id_,
bounds = kBounds1](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(1);
EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
});
window_->SetBoundsInDIP(kBounds1);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_,
bounds = kBounds3](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(1);
EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
});
delegate_.set_on_state_update_callback(
base::BindLambdaForTesting([&]() { window_->SetBoundsInDIP(kBounds3); }));
window_->SetBoundsInDIP(kBounds2);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
}
// Tests that configuring twice with the same state immediately acks and
// commits.
TEST_P(WaylandWindowTest, ConfigureWithSameStateAcksAndCommitsImmediately) {
constexpr gfx::Rect kBounds{123, 234};
auto state = InitializeWlArrayWithActivatedState();
constexpr uint32_t kConfigureSerial1 = 2u;
constexpr uint32_t kConfigureSerial2 = 3u;
constexpr uint32_t kConfigureSerial3 = 4u;
PostToServerAndWait([id = surface_id_,
bounds = kBounds](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
.Times(1);
// TODO(https://crbug.com/443275579): The proper fix should not
// AckConfigure() until the window is mapped.
EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial1)).Times(1);
EXPECT_CALL(*mock_surface, Commit()).Times(0);
});
SendConfigureEvent(surface_id_, kBounds.size(), state, kConfigureSerial1);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
// TODO(https://crbug.com/443275579): The proper fix should not
// AckConfigure() until the window is mapped.
EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial2)).Times(1);
EXPECT_CALL(*mock_surface, Commit()).Times(0);
});
SendConfigureEvent(surface_id_, kBounds.size(), state, kConfigureSerial2);
AdvanceFrameToCurrent(window_.get(), delegate_);
VerifyAndClearExpectations();
// Once window is mapped, commit immediately.
CreateBufferAndPresentAsNewFrame(window_.get(), delegate_,
/*buffer_size=*/kBounds.size(),
/*buffer_scale=*/1.f);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
auto* xdg_surface = mock_surface->xdg_surface();
EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial3)).Times(1);
EXPECT_CALL(*mock_surface, Commit()).Times(1);
});
SendConfigureEvent(surface_id_, kBounds.size(), state, kConfigureSerial3);
VerifyAndClearExpectations();
}
// Test that creates a screen with two displays, with work areas configured to
// be side-by-side horizontally.
class MultiDisplayWaylandWindowTest : public WaylandWindowTest {
public:
static constexpr int64_t kPrimaryDisplayId = 1;
static constexpr int64_t kSecondaryDisplayId = 2;
static constexpr gfx::Rect kPrimaryDisplayBounds{0, 0, 800, 600};
static constexpr gfx::Rect kSecondaryDisplayBounds{800, 0, 800, 600};
// WaylandWindowTest:
void SetUp() override {
test_screen_.display_list().AddDisplay(
display::Display(kPrimaryDisplayId, kPrimaryDisplayBounds),
display::DisplayList::Type::PRIMARY);
test_screen_.display_list().AddDisplay(
display::Display(kSecondaryDisplayId, kSecondaryDisplayBounds),
display::DisplayList::Type::NOT_PRIMARY);
EXPECT_EQ(2, test_screen_.GetNumDisplays());
WaylandWindowTest::SetUp();
}
private:
display::test::TestScreen test_screen_{/*create_display=*/false,
/*register_screen=*/true};
};
// Asserts new windows ignore the display for new windows if bounds have been
// explicitly specified.
TEST_P(MultiDisplayWaylandWindowTest, NewWindowsRespectInitParamBounds) {
MockWaylandPlatformWindowDelegate delegate(connection_.get());
// Set the secondary display as the new window target.
const display::ScopedDisplayForNewWindows scoped_display_new_windows(
kSecondaryDisplayId);
// Init a new window with non-empty bounds.
constexpr gfx::Rect kInitBounds(100, 100, 100, 100);
const auto window = delegate.CreateWaylandWindow(
connection_.get(), PlatformWindowInitProperties(kInitBounds));
ASSERT_TRUE(window);
// Assert the window is placed at the specified bounds, ignoring the display
// for new windows.
EXPECT_EQ(kInitBounds, window->GetBoundsInDIP());
}
class PerSurfaceScaleWaylandWindowTest : public WaylandWindowTest {
public:
PerSurfaceScaleWaylandWindowTest() = default;
~PerSurfaceScaleWaylandWindowTest() override = default;
PerSurfaceScaleWaylandWindowTest(const PerSurfaceScaleWaylandWindowTest&) =
delete;
PerSurfaceScaleWaylandWindowTest& operator=(
const PerSurfaceScaleWaylandWindowTest&) = delete;
void SetUp() override {
CHECK(!base::Contains(
enabled_features_,
base::test::FeatureRef(features::kWaylandPerSurfaceScale)));
enabled_features_.push_back(features::kWaylandPerSurfaceScale);
WaylandWindowTest::SetUp();
}
void TearDown() override {
WaylandWindowTest::TearDown();
CHECK(enabled_features_.back() == features::kWaylandPerSurfaceScale);
enabled_features_.pop_back();
}
};
TEST_P(PerSurfaceScaleWaylandWindowTest, UsePreferredSurfaceScale) {
ASSERT_TRUE(connection_->UsePerSurfaceScaling());
EXPECT_EQ(1u, screen_->GetAllDisplays().size());
EXPECT_EQ(1.0f, screen_->GetDisplayForAcceleratedWidget(window_->GetWidget())
.device_scale_factor());
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
ASSERT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
// GetPreferredScaleFactorForAcceleratedWidget must return the ui scale value
// until the preferred surface scale hasn't been received yet.
EXPECT_FALSE(window_->GetPreferredScaleFactor().has_value());
EXPECT_EQ(
1.0f,
screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
.value_or(0.f));
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
ASSERT_TRUE(mock_surface->fractional_scale());
mock_surface->fractional_scale()->SendPreferredScale(1.5f);
});
SyncDisplay();
Mock::VerifyAndClearExpectations(&delegate_);
// Once preferred surface scale is received, the screen API starts returning
// the composed scale value, ie: ui_scale * window_scale.
EXPECT_EQ(1.5f, window_->GetPreferredScaleFactor().value_or(0.f));
EXPECT_EQ(
1.5f,
screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
.value_or(0.f));
// The preferred scale is then reflected in state's `window_scale` when
// notifying the bounds change.
EXPECT_EQ(1.5f, window_->applied_state().window_scale);
ASSERT_EQ(gfx::Size(1200, 900), window_->applied_state().size_px);
// Ensure state (including scale and bounds) gets latched only when the
// corresponding frame comes in.
EXPECT_EQ(1.0f, window_->latched_state().window_scale);
ASSERT_EQ(gfx::Size(800, 600), window_->latched_state().size_px);
AdvanceFrameToCurrent(window_.get(), delegate_);
EXPECT_EQ(1.5f, window_->latched_state().window_scale);
ASSERT_EQ(gfx::Size(1200, 900), window_->latched_state().size_px);
// GetDisplayForAcceleratedWidget keeps returning the entered output, whose
// scale is unchanged in this case.
EXPECT_EQ(1.0f, screen_->GetDisplayForAcceleratedWidget(window_->GetWidget())
.device_scale_factor());
}
TEST_P(PerSurfaceScaleWaylandWindowTest, UiScale_HandleFontScaleChange) {
ASSERT_TRUE(connection_->IsUiScaleEnabled());
// Required for emulating mouse events.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
// Ensure the initial `window_` and its underlying root surface state is set
// as expected.
EXPECT_EQ(1.0f, window_->applied_state().ui_scale);
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
EXPECT_EQ(window_->applied_state(), window_->latched_state());
// Ensure WaylandScreen::GetPreferredScaleFactorForAcceleratedWidget returns
// the ui scale value while preferred surface scale hasn't been received yet.
EXPECT_FALSE(window_->GetPreferredScaleFactor().has_value());
EXPECT_EQ(
1.0f,
screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
.value_or(0.f));
// Receiving a `wp_fractional_scale_v1::preferred_scale` with scale 1.0
// shouldn't lead to `OnBoundsChanged` calls, as no change did actually occur.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(0);
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
ASSERT_TRUE(mock_surface->fractional_scale());
mock_surface->fractional_scale()->SendPreferredScale(1.0f);
});
WaylandTestBase::SyncDisplay();
Mock::VerifyAndClearExpectations(&delegate_);
// Once preferred surface scale is received, the screen API starts returning
// the composed scale value, ie: ui_scale * window_scale.
EXPECT_EQ(1.0f, window_->GetPreferredScaleFactor().value_or(0.f));
EXPECT_EQ(
1.0f,
screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
.value_or(0.f));
// Setting font scale to 1.25 (which usually happens when 'large-text' system
// setting is turned on) leads to bounds change with the expectations set
// below.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
ASSERT_TRUE(!!connection_->window_manager());
connection_->window_manager()->SetFontScale(1.25f);
Mock::VerifyAndClearExpectations(&delegate_);
// Ensure that once preferred surface scale is received, the screen
// GetPreferredScaleFactorForAcceleratedWidget API returns the composed scale
// value, ie: ui_scale * window_scale.
EXPECT_EQ(1.0f, window_->GetPreferredScaleFactor().value_or(0.f));
EXPECT_EQ(
1.25f,
screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
.value_or(0.f));
EXPECT_EQ(1.25f, window_->applied_state().ui_scale);
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
// DIP size gets downscaled by the composed scale, such that the pixel size
// keeps the same.
EXPECT_EQ(gfx::Size(640, 480), window_->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
EXPECT_EQ(window_->root_surface()->state_.buffer_scale_float, 1.0f);
EXPECT_NE(window_->applied_state(), window_->latched_state());
// Verifies both event dispatching and screen "cursor location" API work as
// expected with ui_scale > 1. Regression test fo https://crbug.com/396457560.
std::unique_ptr<Event> event;
EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly(CloneEvent(&event));
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
auto* pointer_resource = server->seat()->pointer()->resource();
auto* toplevel_surface = server->GetObject<wl::MockSurface>(id);
wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
toplevel_surface->resource(), 0, 0);
wl_pointer_send_motion(pointer_resource, server->GetNextSerial(),
wl_fixed_from_double(100.0f),
wl_fixed_from_double(100.0f));
});
ASSERT_TRUE(event->IsMouseEvent());
ASSERT_EQ(event->type(), ui::EventType::kMouseMoved);
// Event dispatching API expects pixel coordinates.
EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(100, 100));
// Internal cursor position tracker uses Wayland DIP coordinates.
ASSERT_TRUE(connection_->wayland_cursor_position());
EXPECT_EQ(connection_->wayland_cursor_position()->GetCursorSurfacePoint(),
gfx::Point(100, 100));
// Screen API uses UI DIP coordinates.
EXPECT_EQ(screen_->GetCursorScreenPoint(), gfx::Point(80, 80));
// Applied state gets latched when the corresponding produced frame is
// received from Viz and processed by `window_`s frame manager, which is
// emulated in this test by the `CreateShmBasedBuffer` + `CommitOverlays`
// calls below. After that, several expectations are checked, eg: latched
// state, surface state (scale) as well as the relevant wayland requests
// issued during the process.
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
EXPECT_CALL(*mock_surface, Damage(0, 0, 800, 600)).Times(1);
ASSERT_TRUE(mock_surface->viewport());
EXPECT_CALL(*mock_surface->viewport(), SetSource(_, _, _, _)).Times(0);
EXPECT_CALL(*mock_surface->viewport(), SetDestination(_, _)).Times(0);
ASSERT_TRUE(mock_surface->xdg_surface());
EXPECT_CALL(*mock_surface->xdg_surface(),
SetWindowGeometry(gfx::Rect(800, 600)));
EXPECT_CALL(*mock_surface->xdg_surface(), AckConfigure(_)).Times(0);
});
CreateBufferAndPresentAsNewFrame(window_.get(), delegate_,
/*buffer_size=*/gfx::Size(800, 600),
/*buffer_scale=*/1.25f);
// Sync with the test wayland compositor and verify the expectations.
VerifyAndClearExpectations();
EXPECT_EQ(window_->applied_state(), window_->latched_state());
EXPECT_EQ(window_->latched_state().ui_scale, 1.25f);
EXPECT_EQ(window_->latched_state().window_scale, 1.0f);
EXPECT_EQ(window_->root_surface()->state_.buffer_scale_float, 1.0f);
}
TEST_P(PerSurfaceScaleWaylandWindowTest,
UiScale_HandleServerTriggeredBoundsChange) {
ASSERT_TRUE(connection_->IsUiScaleEnabled());
// Initialize surface preferred scale.
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface);
ASSERT_TRUE(mock_surface->fractional_scale());
mock_surface->fractional_scale()->SendPreferredScale(1.0f);
});
WaylandTestBase::SyncDisplay();
EXPECT_EQ(1.0f, window_->GetPreferredScaleFactor().value_or(0));
// Set font scale to 1.25.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
connection_->window_manager()->SetFontScale(1.25f);
VerifyAndClearExpectations();
// Emulate a server-triggered bounds change.
constexpr uint32_t kConfigureSerial = 55u;
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
SendConfigureEvent(surface_id_, gfx::Size(1000, 1000), wl::ScopedWlArray({}),
kConfigureSerial);
VerifyAndClearExpectations();
// Ensure a bounds change request was issued, where DIP bounds is downsized
// proportionally to `ui_scale`, while pixel bounds keeps unchanged.
EXPECT_EQ(1.25f, window_->applied_state().ui_scale);
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
EXPECT_EQ(gfx::Size(800, 800), window_->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(1000, 1000), window_->applied_state().size_px);
// Verify that correct values are used for wayland requests issued when
// applying surface state, even though Viz' buffer scale factor is 1.25, i.e:
// `ui_scale * window_scale`.
PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
EXPECT_CALL(*mock_surface, Damage(0, 0, 1000, 1000));
EXPECT_CALL(*mock_surface->xdg_surface(),
SetWindowGeometry(gfx::Rect(1000, 1000)));
EXPECT_CALL(*mock_surface->xdg_surface(),
AckConfigure(Eq(kConfigureSerial)));
});
CreateBufferAndPresentAsNewFrame(window_.get(), delegate_,
/*buffer_size=*/gfx::Size(1000, 1000),
/*buffer_scale=*/1.25f);
// Sync display and verify the expectations.
VerifyAndClearExpectations();
EXPECT_EQ(window_->latched_state().ui_scale, 1.25f);
EXPECT_EQ(window_->latched_state().window_scale, 1.0f);
EXPECT_EQ(window_->root_surface()->state_.buffer_scale_float, 1.0f);
}
TEST_P(PerSurfaceScaleWaylandWindowTest, UiScale_InitScaleAndBounds) {
ASSERT_TRUE(connection_->IsUiScaleEnabled());
// Set font scale to 1.25.
connection_->window_manager()->SetFontScale(1.25f);
// Create a new toplelvel `window`.
testing::NiceMock<MockWaylandPlatformWindowDelegate> new_window_delegate(
connection_.get());
EXPECT_CALL(new_window_delegate, OnAcceleratedWidgetAvailable(_));
EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(0);
PlatformWindowInitProperties properties(gfx::Rect(800, 800));
auto new_window = new_window_delegate.CreateWaylandWindow(
connection_.get(), std::move(properties));
WaylandTestBase::SyncDisplay();
Mock::VerifyAndClearExpectations(&new_window_delegate);
const uint32_t new_window_surface_id =
new_window->root_surface()->get_surface_id();
ASSERT_NE(new_window_surface_id, 0u);
// Upon initialization, even though the window scale is assumed as 1 (and
// updated asynchronously per wayland events), the UI scale must be set to the
// current font scale straight away (see WaylandWindow::Initialize comments
// for more context) and pixel size must be computed based on the DIP bounds
// passed in.
EXPECT_EQ(gfx::Size(800, 800), new_window->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(1000, 1000), new_window->applied_state().size_px);
EXPECT_EQ(new_window->applied_state().ui_scale, 1.25f);
EXPECT_EQ(new_window->applied_state().window_scale, 1.0f);
EXPECT_EQ(new_window->applied_state(), new_window->latched_state());
PlatformWindowDelegate::State initial_state(new_window->applied_state());
// Ensure ui_scale is returned while preferred surface scale has not been
// received yet.
EXPECT_EQ(1.25f, screen_
->GetPreferredScaleFactorForAcceleratedWidget(
new_window->GetWidget())
.value_or(0.f));
// Request window to be shown and verify initial state is set as expected,
// including ui scale.
EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(0);
new_window->Show(/*inactive=*/false);
Mock::VerifyAndClearExpectations(&new_window_delegate);
CreateBufferAndPresentAsNewFrame(new_window.get(), new_window_delegate,
/*buffer_size=*/gfx::Size(1000, 1000),
/*buffer_scale=*/1.25f);
VerifyAndClearExpectations(new_window_delegate, new_window_surface_id);
EXPECT_EQ(new_window->applied_state(), initial_state);
EXPECT_EQ(new_window->applied_state(), new_window->latched_state());
EXPECT_EQ(new_window->root_surface()->state_.buffer_scale_float, 1.0f);
// Emulate a wayland surface preferred fractional scale of 2.0 for
// `new_window`.
EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(1);
PostToServerAndWait(
[id = new_window_surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(mock_surface->fractional_scale());
mock_surface->fractional_scale()->SendPreferredScale(2.0f);
});
VerifyAndClearExpectations(new_window_delegate, new_window_surface_id);
EXPECT_EQ(gfx::Size(800, 800), new_window->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(2000, 2000), new_window->applied_state().size_px);
EXPECT_EQ(new_window->applied_state().ui_scale, 1.25);
EXPECT_EQ(new_window->applied_state().window_scale, 2.0f);
EXPECT_EQ(2.5, screen_
->GetPreferredScaleFactorForAcceleratedWidget(
new_window->GetWidget())
.value_or(0.f));
// Send the initial activation configure events sequence, with (0, 0) size,
// such that the client-requested size is used, i.e (1000, 1000) set above.
// Then, emulate a new frame coming from Viz and verify the correct Wayland
// requests and parameters are used in response to it.
constexpr uint32_t kConfigureSerial = 11u;
EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(0);
PostToServerAndWait([id = new_window_surface_id](
wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
EXPECT_CALL(*mock_surface, Damage(0, 0, 1000, 1000));
EXPECT_CALL(*mock_surface->xdg_surface(),
AckConfigure(Eq(kConfigureSerial)));
// No new xdg_surface.set_window_geometry requests as DIP geometry
// has not changed.
EXPECT_CALL(*mock_surface->xdg_surface(), SetWindowGeometry(_)).Times(0);
});
SendConfigureEvent(new_window_surface_id, gfx::Size(0, 0),
wl::ScopedWlArray({XDG_TOPLEVEL_STATE_ACTIVATED}),
kConfigureSerial);
CreateBufferAndPresentAsNewFrame(new_window.get(), new_window_delegate,
/*buffer_size=*/gfx::Size(2000, 2000),
/*buffer_scale=*/2.5f);
// Sync display and verify the expectations.
VerifyAndClearExpectations(new_window_delegate, new_window_surface_id);
EXPECT_EQ(new_window->applied_state(), new_window->latched_state());
EXPECT_EQ(new_window->latched_state().ui_scale, 1.25f);
EXPECT_EQ(new_window->latched_state().window_scale, 2.0f);
EXPECT_EQ(new_window->root_surface()->state_.buffer_scale_float, 2.0f);
}
TEST_P(PerSurfaceScaleWaylandWindowTest, UiScale_HandlePopupGeometry) {
ASSERT_TRUE(connection_->IsUiScaleEnabled());
// Required for emulating mouse events.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
wl_seat_send_capabilities(server->seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
});
ASSERT_TRUE(connection_->seat()->pointer());
// Set font scale to 1.25.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
connection_->window_manager()->SetFontScale(1.25f);
Mock::VerifyAndClearExpectations(&delegate_);
WaylandTestBase::SyncDisplay();
EXPECT_EQ(1.25f, window_->applied_state().ui_scale);
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
EXPECT_EQ(gfx::Size(640, 480), window_->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
// Initialize create and show a 20x80 popup at (100, 100) ui size/location
// (1.25 ui inv-scaled). So it must be positioned at (125, 125) with 25x100
// dip wayland pixels.
auto* toplevel = window_.get();
testing::NiceMock<MockWaylandPlatformWindowDelegate> menu_delegate(
connection_.get());
ui::OwnedWindowAnchor anchor{
.anchor_rect = gfx::Rect(100, 100, 20, 20),
.anchor_position = OwnedWindowAnchorPosition::kBottomRight,
.anchor_gravity = OwnedWindowAnchorGravity::kBottomLeft};
EXPECT_CALL(menu_delegate, GetOwnedWindowAnchorAndRectInDIP())
.WillOnce(Return(anchor));
auto menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::Rect(100, 100, 20, 80), &menu_delegate,
toplevel->GetWidget());
ASSERT_TRUE(menu_window);
const uint32_t menu_surface_id =
menu_window->root_surface()->get_surface_id();
PostToServerAndWait([menu_surface_id](wl::TestWaylandServerThread* server) {
auto* popup = GetTestXdgPopupByWindow(server, menu_surface_id);
ASSERT_TRUE(popup);
EXPECT_EQ(popup->anchor_rect(), gfx::Rect(125, 125, 25, 25));
EXPECT_EQ(popup->size(), gfx::Size(25, 100));
});
PlatformWindowDelegate::State initial_state(menu_window->applied_state());
// Emulate a popup configure with empty geometry rectangle, in which case the
// current popup bounds is expected to be used.
SendConfigureEvent(menu_surface_id, gfx::Size(0, 0), wl::ScopedWlArray({}));
EXPECT_EQ(initial_state.ToString(), menu_window->applied_state().ToString());
// Now emulate a popup configure with a 100x200 geometry, and verifies that:
// 1. A state change request is triggered with the ui_scale'd equivalent dip
// geometry 80x160.
// 2. The expected wayland requests (along with the correct geometry and scale
// related parameters) are issued once the new frame is received from Viz.
const uint32_t kLatestConfigureSerial = 11u;
PostToServerAndWait([menu_surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* mock_surface =
server->GetObject<wl::MockSurface>(menu_surface_id);
ASSERT_TRUE(mock_surface->xdg_surface());
EXPECT_CALL(*mock_surface, Damage(0, 0, 100, 200));
EXPECT_CALL(*mock_surface->xdg_surface(),
SetWindowGeometry(gfx::Rect(100, 200)));
EXPECT_CALL(*mock_surface->xdg_surface(),
AckConfigure(Eq(kLatestConfigureSerial)));
});
// Configure popup window to origin 100,100 and size 100x200.
PostToServerAndWait([id = menu_surface_id](
wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(id);
ASSERT_TRUE(surface);
auto* xdg_surface = surface->xdg_surface();
ASSERT_TRUE(xdg_surface);
ASSERT_TRUE(xdg_surface->xdg_popup()->resource());
xdg_popup_send_configure(xdg_surface->xdg_popup()->resource(), 100, 100,
100, 200);
xdg_surface_send_configure(xdg_surface->resource(), kLatestConfigureSerial);
});
EXPECT_EQ(menu_window->applied_state().bounds_dip,
gfx::Rect(80, 80, 80, 160));
CreateBufferAndPresentAsNewFrame(menu_window.get(), menu_delegate,
/*buffer_size=*/gfx::Size(100, 200),
/*buffer_scale=*/1.25f);
// Sync display and verify the expectations.
VerifyAndClearExpectations(menu_delegate, menu_surface_id);
EXPECT_EQ(menu_window->applied_state(), menu_window->latched_state());
EXPECT_EQ(menu_window->latched_state().ui_scale, 1.25f);
EXPECT_EQ(menu_window->latched_state().window_scale, 1.0f);
EXPECT_EQ(menu_window->root_surface()->state_.buffer_scale_float, 1.0f);
// Verifies both event dispatching and screen "cursor location" API works` as
// expected with ui_scale > 1. Regression test fo https://crbug.com/396457560.
std::unique_ptr<Event> event;
EXPECT_CALL(menu_delegate, DispatchEvent(_))
.WillRepeatedly(CloneEvent(&event));
PostToServerAndWait(
[id = menu_surface_id](wl::TestWaylandServerThread* server) {
auto* pointer_resource = server->seat()->pointer()->resource();
auto* menu_surface = server->GetObject<wl::MockSurface>(id);
wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
menu_surface->resource(), 0, 0);
wl_pointer_send_motion(pointer_resource, server->GetNextSerial(),
wl_fixed_from_double(10.0f),
wl_fixed_from_double(10.0f));
});
ASSERT_TRUE(event);
ASSERT_TRUE(event->IsMouseEvent());
ASSERT_EQ(event->type(), ui::EventType::kMouseMoved);
// Event dispatching API expects pixel coordinates.
EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 10));
EXPECT_EQ(event->AsLocatedEvent()->root_location(), gfx::Point(10, 10));
// Internal cursor position tracker uses Wayland DIP coordinates.
ASSERT_TRUE(connection_->wayland_cursor_position());
EXPECT_EQ(connection_->wayland_cursor_position()->GetCursorSurfacePoint(),
gfx::Point(110, 110));
// Screen API uses UI DIP coordinates.
EXPECT_EQ(screen_->GetCursorScreenPoint(), gfx::Point(88, 88));
}
TEST_P(PerSurfaceScaleWaylandWindowTest, UiScale_SanitizeFontScale) {
ASSERT_TRUE(connection_->IsUiScaleEnabled());
auto test_font_scale = [&](float requested_font_scale,
float sanitized_font_scale) {
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
connection_->window_manager()->SetFontScale(requested_font_scale);
Mock::VerifyAndClearExpectations(&delegate_);
WaylandTestBase::SyncDisplay();
EXPECT_EQ(sanitized_font_scale, window_->applied_state().ui_scale);
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
};
// Set arbitrary font scale values and verify expectations.
test_font_scale(20.0f, 3.0f);
test_font_scale(0.10f, 0.5f);
test_font_scale(1.5f, 1.5f);
test_font_scale(-1.0f, 0.5f);
}
TEST_P(PerSurfaceScaleWaylandWindowTest, UiScale_ForceDeviceScaleFactor) {
// When enabled, it must take precedence over font scale.
ASSERT_TRUE(connection_->IsUiScaleEnabled());
display::Display::SetForceDeviceScaleFactor(2.0);
EXPECT_EQ(2.0f, connection_->window_manager()->DetermineUiScale());
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
SendConfigureEvent(surface_id_, gfx::Size(1000, 1000), wl::ScopedWlArray({}));
VerifyAndClearExpectations();
// Ensure the forced scale factor takes precedence (over font scale) and
// gets used as ui scale when firing the bounds change triggered by the
// server, in this case.
EXPECT_EQ(2.0f, window_->applied_state().ui_scale);
EXPECT_EQ(1.0f, window_->applied_state().window_scale);
EXPECT_EQ(gfx::Size(500, 500), window_->applied_state().bounds_dip.size());
EXPECT_EQ(gfx::Size(1000, 1000), window_->applied_state().size_px);
PlatformWindowDelegate::State previous_state(window_->applied_state());
// Font scale changes should not trigger bounds change when
// force-device-scale-factor is in use.
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
connection_->window_manager()->SetFontScale(1.25f);
Mock::VerifyAndClearExpectations(&delegate_);
EXPECT_EQ(2.0f, connection_->window_manager()->DetermineUiScale());
EXPECT_EQ(window_->applied_state(), previous_state);
}
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandWindowTest,
Values(wl::ServerConfig{}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
MultiDisplayWaylandWindowTest,
Values(wl::ServerConfig{}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
PerSurfaceScaleWaylandWindowTest,
Values(wl::ServerConfig{
.supports_viewporter_surface_scaling = true}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandSubsurfaceTest,
Values(wl::ServerConfig{}));
} // namespace ui