blob: 4a313131d1afe9a50f1a6af92e75dea21a9990cb [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "base/memory/raw_ptr.h"
#include <cstddef>
#include <memory>
#include <utility>
#include <vector>
#include <cursor-shapes-unstable-v1-client-protocol.h>
#include <linux/input.h>
#include <wayland-server-core.h>
#include <xdg-shell-server-protocol.h>
#include <xdg-shell-unstable-v6-server-protocol.h>
#include "base/environment.h"
#include "base/files/file_util.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/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "build/chromeos_buildflags.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/ui_base_types.h"
#include "ui/display/display.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.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_widget_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_util.h"
#include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
#include "ui/ozone/platform/wayland/host/wayland_connection_test_api.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/host/wayland_zcr_cursor_shapes.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/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_test.h"
#include "ui/ozone/public/ozone_switches.h"
#include "ui/platform_window/platform_window.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::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;
constexpr uint32_t kAugmentedSurfaceNotSupportedVersion = 0;
struct PopupPosition {
gfx::Rect anchor_rect;
gfx::Size size;
uint32_t anchor = 0;
uint32_t gravity = 0;
uint32_t constraint_adjustment = 0;
};
class ScopedWlArray {
public:
ScopedWlArray() { wl_array_init(&array_); }
ScopedWlArray(ScopedWlArray&& rhs) {
array_ = rhs.array_;
// wl_array_init sets rhs.array_'s fields to nullptr, so that
// the free() in wl_array_release() is a no-op.
wl_array_init(&rhs.array_);
}
~ScopedWlArray() { wl_array_release(&array_); }
ScopedWlArray& operator=(ScopedWlArray&& rhs) {
wl_array_release(&array_);
array_ = rhs.array_;
// wl_array_init sets rhs.array_'s fields to nullptr, so that
// the free() in wl_array_release() is a no-op.
wl_array_init(&rhs.array_);
return *this;
}
wl_array* get() { return &array_; }
private:
wl_array array_;
};
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());
}
class MockZcrCursorShapes : public WaylandZcrCursorShapes {
public:
MockZcrCursorShapes() : WaylandZcrCursorShapes(nullptr, nullptr) {}
MockZcrCursorShapes(const MockZcrCursorShapes&) = delete;
MockZcrCursorShapes& operator=(const MockZcrCursorShapes&) = delete;
~MockZcrCursorShapes() override = default;
MOCK_METHOD(void, SetCursorShape, (int32_t), (override));
};
using BoundsChange = PlatformWindowDelegate::BoundsChange;
constexpr BoundsChange kDefaultBoundsChange{false};
} // namespace
class WaylandWindowTest : public WaylandTest {
public:
WaylandWindowTest()
: test_mouse_event_(ET_MOUSE_PRESSED,
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();
xdg_surface_ = surface_->xdg_surface();
ASSERT_TRUE(xdg_surface_);
}
protected:
void SendConfigureEventPopup(WaylandWindow* menu_window,
const gfx::Rect& bounds) {
auto* popup = GetTestXdgPopupByWindow(menu_window);
ASSERT_TRUE(popup);
if (GetParam().shell_version == wl::ShellVersion::kV6) {
zxdg_popup_v6_send_configure(popup->resource(), bounds.x(), bounds.y(),
bounds.width(), bounds.height());
} else {
xdg_popup_send_configure(popup->resource(), bounds.x(), bounds.y(),
bounds.width(), bounds.height());
}
}
wl::MockXdgTopLevel* GetXdgToplevel() { return xdg_surface_->xdg_toplevel(); }
void AddStateToWlArray(uint32_t state, wl_array* states) {
*static_cast<uint32_t*>(wl_array_add(states, sizeof state)) = state;
}
ScopedWlArray InitializeWlArrayWithActivatedState() {
ScopedWlArray states;
AddStateToWlArray(XDG_TOPLEVEL_STATE_ACTIVATED, states.get());
return states;
}
ScopedWlArray MakeStateArray(const std::vector<int32_t> states) {
ScopedWlArray result;
for (const auto state : states)
AddStateToWlArray(state, result.get());
return result;
}
std::unique_ptr<WaylandWindow> CreateWaylandWindowWithParams(
PlatformWindowType type,
gfx::AcceleratedWidget parent_widget,
const gfx::Rect& bounds,
MockWaylandPlatformWindowDelegate* delegate) {
PlatformWindowInitProperties properties;
// TODO(msisov): use a fancy method to calculate position of a popup window.
properties.bounds = bounds;
properties.type = type;
properties.parent_widget = parent_widget;
auto window = delegate->CreateWaylandWindow(
connection_.get(), std::move(properties), true, true);
if (window)
window->Show(false);
return window;
}
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));
}
MockZcrCursorShapes* InstallMockZcrCursorShapes() {
auto zcr_cursor_shapes = std::make_unique<MockZcrCursorShapes>();
MockZcrCursorShapes* mock_cursor_shapes = zcr_cursor_shapes.get();
WaylandConnectionTestApi test_api(connection_.get());
test_api.SetZcrCursorShapes(std::move(zcr_cursor_shapes));
return mock_cursor_shapes;
}
void VerifyAndClearExpectations() {
Mock::VerifyAndClearExpectations(xdg_surface_);
Mock::VerifyAndClearExpectations(&delegate_);
}
void VerifyXdgPopupPosition(WaylandWindow* menu_window,
const PopupPosition& position) {
auto* popup = GetTestXdgPopupByWindow(menu_window);
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_->wayland_window_manager()->GetCurrentPointerFocusedWindow();
DCHECK(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) {
DCHECK(dispatching_windows.size() < 2);
auto* touch_focused_window =
connection_->wayland_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(ET_TOUCH_PRESSED, {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) {
DCHECK(dispatching_windows.size() < 2);
auto* keyboard_focused_window = connection_->wayland_window_manager()
->GetCurrentKeyboardFocusedWindow();
// There must be focused window to dispatch.
if (dispatching_windows.size() == 0)
EXPECT_FALSE(keyboard_focused_window);
KeyEvent test_key_event(ET_KEY_PRESSED, 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));
}
}
wl::TestXdgPopup* GetTestXdgPopupByWindow(WaylandWindow* window) {
wl::MockSurface* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
if (mock_surface) {
auto* mock_xdg_surface = mock_surface->xdg_surface();
if (mock_xdg_surface)
return mock_xdg_surface->xdg_popup();
}
return nullptr;
}
raw_ptr<wl::MockXdgSurface> xdg_surface_;
MouseEvent test_mouse_event_;
};
TEST_P(WaylandWindowTest, SetTitle) {
EXPECT_CALL(*GetXdgToplevel(), SetTitle(StrEq("hello")));
window_->SetTitle(u"hello");
}
TEST_P(WaylandWindowTest, UpdateVisualSizeConfiguresWaylandWindow) {
constexpr gfx::Rect kNormalBounds{500, 300};
uint32_t serial = 0;
auto state = InitializeWlArrayWithActivatedState();
window_->set_update_visual_size_immediately_for_testing(false);
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
// Configure event makes Wayland update bounds, but does not change toplevel
// input region, opaque region or window geometry immediately. Such actions
// are postponed to UpdateVisualSize();
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(kNormalBounds.size())))
.Times(0);
EXPECT_CALL(*xdg_surface_, AckConfigure(1)).Times(0);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
SendConfigureEvent(xdg_surface_, kNormalBounds.size(), ++serial, state.get());
Sync();
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds.size())));
EXPECT_CALL(*xdg_surface_, AckConfigure(1));
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
EXPECT_CALL(*mock_surface, SetInputRegion(_));
window_->UpdateVisualSize(kNormalBounds.size());
}
// WaylandSurface state changes are sent to wayland compositor when
// ApplyPendingState() is called.
TEST_P(WaylandWindowTest, ApplyPendingStatesAndCommit) {
window_->set_update_visual_size_immediately_for_testing(false);
window_->set_apply_pending_state_on_update_visual_size_for_testing(false);
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
// 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()->SetOpaqueRegion(&region_px);
window_->root_surface()->SetInputRegion(region_px.data());
window_->root_surface()->SetSurfaceBufferScale(2);
Sync();
// 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();
Sync();
}
// Checks that decoration insets do not change final bounds and that
// WaylandToplevelWindow::HandleToplevelConfigure does correct rounding when
// some sides of insets divides by 2 with remainder.
TEST_P(WaylandWindowTest, SetDecorationInsets) {
constexpr gfx::Rect kNormalBounds{956, 556};
const auto kHiDpiScale = 2;
const gfx::Size kHiDpiSize =
gfx::ScaleToRoundedRect(kNormalBounds, kHiDpiScale).size();
const BoundsChange kHiDpiBounds{true};
window_->SetBoundsInDIP(kNormalBounds);
uint32_t serial = 0;
auto state = InitializeWlArrayWithActivatedState();
// Creating an output with scale 2.
wl::TestOutput* output = server_.output();
// Send the window to |output1|.
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), output->resource());
Sync();
// Set insets for normal DPI.
const auto kDecorationInsets = gfx::Insets::TLBR(24, 28, 32, 28);
auto bounds_with_insets = kNormalBounds;
bounds_with_insets.Inset(kDecorationInsets);
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(bounds_with_insets));
window_->SetDecorationInsets(&kDecorationInsets);
// Setting the decoration insets does not trigger the immediate update of the
// window geometry. Emulate updating the visual size (sending the frame
// update) for that.
window_->UpdateVisualSize(kNormalBounds.size());
// Setting geometry is double buffered and requires commit called on the
// surface. In production, this is handled during commit of a next frame
// (which also updates visual size).
window_->root_surface()->Commit();
Sync();
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(bounds_with_insets));
SendConfigureEvent(xdg_surface_, bounds_with_insets.size(), ++serial,
state.get());
Sync();
// Change scale. This is the only time when we expect the pixel position to
// change.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kHiDpiBounds))).Times(1);
output->SetScale(kHiDpiScale);
output->Flush();
Sync();
// Pretend we are already rendering using new scale.
window_->root_surface()->SetSurfaceBufferScale(kHiDpiScale);
// Set new insets so that rounding does not result in integer.
constexpr auto kDecorationInsets_2x = gfx::Insets::TLBR(48, 55, 63, 55);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(bounds_with_insets));
window_->SetDecorationInsets(&kDecorationInsets_2x);
// Setting the decoration insets does not trigger the immediate update of the
// window geometry. Emulate updating the visual size (sending the frame
// update) for that.
window_->UpdateVisualSize(kHiDpiSize);
// Setting geometry is double buffered and requires commit called on the
// surface. In production, this is handled during commit of a next frame
// (which also updates visual size).
window_->root_surface()->Commit();
Sync();
// Now send configure events many times - bounds mustn't change.
for (size_t i = 0; i < 10; i++) {
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(bounds_with_insets));
SendConfigureEvent(xdg_surface_, bounds_with_insets.size(), ++serial,
state.get());
Sync();
}
}
// Checks that when the window gets some of its edges tiled, it notifies the
// delegate appropriately.
TEST_P(WaylandWindowTest, HandleTiledEdges) {
// Only the stable XDG shell protocol supports tiled states.
if (GetParam().shell_version == wl::ShellVersion::kV6)
GTEST_SKIP();
constexpr gfx::Rect kWindowBounds{800, 600};
uint32_t serial = 0;
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(xdg_surface_, kWindowBounds.size(), ++serial,
configured_states.get());
Sync();
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;
window_->set_update_visual_size_immediately_for_testing(false);
// Send 3 configures, and call UpdateVisualSize out of order. The out-of-order
// UpdateVisualSize(kNormalBounds2) should disregarded b/c kNormalBounds2
// never reached UI Compositor when UpdateVisualSize(kNormalBounds2) is
// called.
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds1.size())));
EXPECT_CALL(*xdg_surface_, AckConfigure(2));
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds2.size())))
.Times(0);
EXPECT_CALL(*xdg_surface_, AckConfigure(3)).Times(0);
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds3.size())));
EXPECT_CALL(*xdg_surface_, AckConfigure(4));
auto state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds1.size(), ++serial,
state.get());
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds2.size(), ++serial,
state.get());
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds3.size(), ++serial,
state.get());
Sync();
window_->UpdateVisualSize(kNormalBounds2.size());
window_->UpdateVisualSize(kNormalBounds1.size());
window_->UpdateVisualSize(kNormalBounds3.size());
}
TEST_P(WaylandWindowTest, MismatchUpdateVisualSize) {
constexpr gfx::Rect kNormalBounds1{500, 300};
constexpr gfx::Rect kNormalBounds2{800, 600};
constexpr gfx::Rect kNormalBounds3{700, 400};
uint32_t serial = 1;
window_->set_update_visual_size_immediately_for_testing(false);
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
// UpdateVisualSize with different size 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(_));
EXPECT_CALL(*mock_surface, SetInputRegion(_));
auto state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds1.size(), ++serial,
state.get());
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds2.size(), ++serial,
state.get());
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds3.size(), ++serial,
state.get());
Sync();
window_->UpdateVisualSize({100, 100});
}
TEST_P(WaylandWindowTest, UpdateVisualSizeClearsPreviousUnackedConfigures) {
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();
window_->set_update_visual_size_immediately_for_testing(false);
// Send 3 configures. Calling UpdateVisualSize(kNormalBounds3) will cause the
// kNormalBounds3 to be passed onto UI compositor. Hence, kNormalBounds1/2/3
// configs will be acknowledgeable. The next UpdateVisualSize(kNormalBounds3)
// will ack kNormalBounds3 and skip kNormalBounds1/2.
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds1.size())))
.Times(0);
EXPECT_CALL(*xdg_surface_, AckConfigure(2)).Times(0);
SendConfigureEvent(xdg_surface_, kNormalBounds1.size(), ++serial,
state.get());
Sync();
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds2.size())))
.Times(0);
EXPECT_CALL(*xdg_surface_, AckConfigure(3)).Times(0);
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds2.size(), ++serial,
state.get());
Sync();
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds3.size())));
EXPECT_CALL(*xdg_surface_, AckConfigure(4));
state = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, kNormalBounds3.size(), ++serial,
state.get());
Sync();
window_->UpdateVisualSize(kNormalBounds3.size());
window_->UpdateVisualSize(kNormalBounds3.size());
}
TEST_P(WaylandWindowTest, MaximizeAndRestore) {
constexpr gfx::Rect kNormalBounds{500, 300};
constexpr gfx::Rect kMaximizedBounds{800, 600};
uint32_t serial = 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());
VerifyAndClearExpectations();
// Deactivate the surface.
auto empty_state = MakeStateArray({});
SendConfigureEvent(xdg_surface_, {0, 0}, ++serial, empty_state.get());
Sync();
auto active_maximized = MakeStateArray(
{XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
EXPECT_CALL(*GetXdgToplevel(), SetMaximized());
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kMaximizedBounds.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)
.WillOnce(
testing::Invoke([this]() { window_->SetDecorationInsets({}); }));
window_->Maximize();
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), ++serial,
active_maximized.get());
Sync();
VerifyAndClearExpectations();
auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED});
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kMaximizedBounds.size())));
EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), ++serial,
inactive_maximized.get());
Sync();
VerifyAndClearExpectations();
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kMaximizedBounds.size())));
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), ++serial,
active_maximized.get());
Sync();
VerifyAndClearExpectations();
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds.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)
.WillOnce(
testing::Invoke([this]() { window_->SetDecorationInsets({}); }));
EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
EXPECT_CALL(*GetXdgToplevel(), UnsetMaximized());
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
auto active = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, ++serial, active.get());
Sync();
}
TEST_P(WaylandWindowTest, Minimize) {
ScopedWlArray states;
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
SendConfigureEvent(xdg_surface_, {0, 0}, 1, states.get());
Sync();
EXPECT_CALL(*GetXdgToplevel(), SetMinimized());
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Minimize();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
// Reinitialize wl_array, which removes previous old states.
states = ScopedWlArray();
SendConfigureEvent(xdg_surface_, {0, 0}, 2, states.get());
Sync();
// 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(xdg_surface_, {0, 0}, 3, states.get());
Sync();
// And one last time to ensure the behaviour.
SendConfigureEvent(xdg_surface_, {0, 0}, 4, states.get());
Sync();
}
TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
// Make sure the window is initialized to normal state from the beginning.
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 1, states.get());
Sync();
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
EXPECT_CALL(*GetXdgToplevel(), SetFullscreen());
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->ToggleFullscreen();
// Make sure than WaylandWindow manually handles fullscreen states. Check the
// comment in the WaylandWindow::ToggleFullscreen.
EXPECT_EQ(window_->GetPlatformWindowState(),
PlatformWindowState::kFullScreen);
SendConfigureEvent(xdg_surface_, {0, 0}, 2, states.get());
Sync();
EXPECT_CALL(*GetXdgToplevel(), UnsetFullscreen());
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Restore();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 3, states.get());
Sync();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
}
TEST_P(WaylandWindowTest, StartWithFullscreen) {
MockWaylandPlatformWindowDelegate delegate;
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), true, true);
Sync();
// 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.
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
EXPECT_FALSE(mock_surface->xdg_surface());
EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
window->ToggleFullscreen();
// The state of the window must already be fullscreen one.
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
Sync();
// We mustn't receive any state changes if that does not differ from the last
// state.
EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
// Activate the surface.
ScopedWlArray states = InitializeWlArrayWithActivatedState();
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
SendConfigureEvent(xdg_surface_, {0, 0}, 1, states.get());
Sync();
// It must be still the same state.
EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
}
TEST_P(WaylandWindowTest, StartMaximized) {
MockWaylandPlatformWindowDelegate delegate;
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), true, true);
Sync();
// 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.
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
EXPECT_FALSE(mock_surface->xdg_surface());
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Maximize();
// The state of the window must already be fullscreen one.
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
Sync();
// 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.
ScopedWlArray states = InitializeWlArrayWithActivatedState();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(xdg_surface_, {0, 0}, 1, states.get());
Sync();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
}
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
window_->SetDecorationInsets(&kInsets);
ScopedWlArray states = InitializeWlArrayWithActivatedState();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(xdg_surface_, {2000, 2000}, 1, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
.Times(1);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect{2000, 2000}));
Sync();
EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
// Unmaximize
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 2, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
.Times(1);
EXPECT_CALL(
*xdg_surface_,
SetWindowGeometry(gfx::Rect(
kInsets.left(), kInsets.top(),
kNormalBounds.width() - (kInsets.left() + kInsets.right()),
kNormalBounds.height() - (kInsets.top() + kInsets.bottom()))));
Sync();
// Now, set to fullscreen.
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
SendConfigureEvent(xdg_surface_, {2005, 2005}, 3, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
.Times(1);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(2005, 2005)));
Sync();
// Unfullscreen
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 4, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
.Times(1);
EXPECT_CALL(
*xdg_surface_,
SetWindowGeometry(gfx::Rect(
kInsets.left(), kInsets.top(),
kNormalBounds.width() - (kInsets.left() + kInsets.right()),
kNormalBounds.height() - (kInsets.top() + kInsets.bottom()))));
Sync();
// Now, maximize, fullscreen and restore.
states = InitializeWlArrayWithActivatedState();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(xdg_surface_, {2000, 2000}, 1, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
.Times(1);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(2000, 2000)));
Sync();
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
SendConfigureEvent(xdg_surface_, {2005, 2005}, 1, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
.Times(1);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(2005, 2005)));
// Restore
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 4, states.get());
EXPECT_CALL(delegate_,
OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
.Times(1);
EXPECT_CALL(
*xdg_surface_,
SetWindowGeometry(gfx::Rect(
kInsets.left(), kInsets.top(),
kNormalBounds.width() - (kInsets.left() + kInsets.right()),
kNormalBounds.height() - (kInsets.top() + kInsets.bottom()))));
Sync();
}
TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) {
constexpr gfx::Rect kNormalBounds{500, 300};
constexpr gfx::Rect kMaximizedBounds{800, 600};
uint32_t serial = 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());
VerifyAndClearExpectations();
// Deactivate the surface.
ScopedWlArray empty_state;
SendConfigureEvent(xdg_surface_, {0, 0}, ++serial, empty_state.get());
Sync();
auto active_maximized = MakeStateArray(
{XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
EXPECT_CALL(*GetXdgToplevel(), SetMaximized());
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kMaximizedBounds.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(xdg_surface_, kMaximizedBounds.size(), ++serial,
active_maximized.get());
Sync();
// Verify that the state has not been changed.
EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
VerifyAndClearExpectations();
EXPECT_CALL(*GetXdgToplevel(), SetFullscreen());
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kMaximizedBounds.size())));
EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->ToggleFullscreen();
// State changes are synchronous.
EXPECT_EQ(PlatformWindowState::kFullScreen,
window_->GetPlatformWindowState());
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, active_maximized.get());
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), ++serial,
active_maximized.get());
Sync();
// Verify that the state has not been changed.
EXPECT_EQ(PlatformWindowState::kFullScreen,
window_->GetPlatformWindowState());
VerifyAndClearExpectations();
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(kNormalBounds.size())));
EXPECT_CALL(*GetXdgToplevel(), UnsetFullscreen());
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(xdg_surface_, {0, 0}, ++serial, active.get());
Sync();
EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
}
TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximize) {
const gfx::Rect current_bounds = window_->GetBoundsInDIP();
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();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), 1, states.get());
Sync();
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.
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect{current_bounds.size()}));
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 2, states.get());
Sync();
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();
ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 1, states.get());
Sync();
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_->ToggleFullscreen();
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
SendConfigureEvent(xdg_surface_, kFullscreenBounds.size(), 2, states.get());
Sync();
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.
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(current_bounds.size())));
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 3, states.get());
Sync();
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();
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();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), 1, states.get());
Sync();
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(bounds, restored_bounds);
constexpr gfx::Rect kFullscreenBounds(1280, 720);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
window_->ToggleFullscreen();
AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get());
SendConfigureEvent(xdg_surface_, kFullscreenBounds.size(), 2, states.get());
Sync();
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();
AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
SendConfigureEvent(xdg_surface_, kMaximizedBounds.size(), 3, states.get());
Sync();
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.
EXPECT_CALL(*xdg_surface_,
SetWindowGeometry(gfx::Rect(current_bounds.size())));
window_->Restore();
// Reinitialize wl_array, which removes previous old states.
states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 4, states.get());
Sync();
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));
window_->SetBoundsInDIP(new_bounds);
ScopedWlArray states = InitializeWlArrayWithActivatedState();
// First case is when Wayland sends a configure event with 0,0 height and
// width.
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(2);
SendConfigureEvent(xdg_surface_, {0, 0}, 2, states.get());
Sync();
// Restored bounds should keep empty value.
gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
// Second case is when Wayland sends a configure event with 1, 1 height and
// width. It looks more like a bug in Gnome Shell with Wayland as long as the
// documentation says it must be set to 0, 0, when wayland requests bounds.
SendConfigureEvent(xdg_surface_, {0, 0}, 3, states.get());
Sync();
// Restored bounds should keep empty value.
restored_bounds = window_->GetRestoredBoundsInDIP();
EXPECT_EQ(restored_bounds, gfx::Rect());
}
TEST_P(WaylandWindowTest, UpdateWindowRegion) {
wl::MockSurface* mock_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
// Change bounds.
const gfx::Rect initial_bounds = window_->GetBoundsInDIP();
const gfx::Rect new_bounds =
gfx::Rect(initial_bounds.width() + 10, initial_bounds.height() + 10);
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
window_->SetBoundsInDIP(new_bounds);
Sync();
VerifyAndClearExpectations();
EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
EXPECT_EQ(mock_surface->input_region(), new_bounds);
// Maximize.
ScopedWlArray states = InitializeWlArrayWithActivatedState();
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(xdg_surface_, kMaximizedBounds.size(), 1, states.get());
Sync();
VerifyAndClearExpectations();
EXPECT_EQ(mock_surface->opaque_region(), kMaximizedBounds);
EXPECT_EQ(mock_surface->input_region(), kMaximizedBounds);
// Restore.
const gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
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(xdg_surface_, {0, 0}, 2, active.get());
Sync();
VerifyAndClearExpectations();
EXPECT_EQ(mock_surface->opaque_region(), restored_bounds);
EXPECT_EQ(mock_surface->input_region(), restored_bounds);
}
TEST_P(WaylandWindowTest, CanDispatchMouseEventFocus) {
// SetPointerFocusedWindow requires a WaylandPointer.
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
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, SetCursorUsesZcrCursorShapesForCommonTypes) {
MockZcrCursorShapes* mock_cursor_shapes = InstallMockZcrCursorShapes();
// Verify some commonly-used cursors.
EXPECT_CALL(*mock_cursor_shapes,
SetCursorShape(ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_POINTER));
auto pointer_cursor = base::MakeRefCounted<BitmapCursor>(
mojom::CursorType::kPointer, kDefaultCursorScale);
window_->SetCursor(pointer_cursor.get());
EXPECT_CALL(*mock_cursor_shapes,
SetCursorShape(ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_HAND));
auto hand_cursor = base::MakeRefCounted<BitmapCursor>(
mojom::CursorType::kHand, kDefaultCursorScale);
window_->SetCursor(hand_cursor.get());
EXPECT_CALL(*mock_cursor_shapes,
SetCursorShape(ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_IBEAM));
auto ibeam_cursor = base::MakeRefCounted<BitmapCursor>(
mojom::CursorType::kIBeam, kDefaultCursorScale);
window_->SetCursor(ibeam_cursor.get());
}
TEST_P(WaylandWindowTest, SetCursorCallsZcrCursorShapesOncePerCursor) {
MockZcrCursorShapes* mock_cursor_shapes = InstallMockZcrCursorShapes();
auto hand_cursor = base::MakeRefCounted<BitmapCursor>(
mojom::CursorType::kHand, kDefaultCursorScale);
// Setting the same cursor twice on the client only calls the server once.
EXPECT_CALL(*mock_cursor_shapes, SetCursorShape(_)).Times(1);
window_->SetCursor(hand_cursor.get());
window_->SetCursor(hand_cursor.get());
}
TEST_P(WaylandWindowTest, SetCursorDoesNotUseZcrCursorShapesForNoneCursor) {
MockZcrCursorShapes* mock_cursor_shapes = InstallMockZcrCursorShapes();
EXPECT_CALL(*mock_cursor_shapes, SetCursorShape(_)).Times(0);
auto none_cursor = base::MakeRefCounted<BitmapCursor>(
mojom::CursorType::kNone, kDefaultCursorScale);
window_->SetCursor(none_cursor.get());
}
TEST_P(WaylandWindowTest, SetCursorDoesNotUseZcrCursorShapesForCustomCursors) {
MockZcrCursorShapes* mock_cursor_shapes = InstallMockZcrCursorShapes();
// Custom cursors require bitmaps, so they do not use server-side cursors.
EXPECT_CALL(*mock_cursor_shapes, SetCursorShape(_)).Times(0);
auto custom_cursor =
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, ConfigureEvent) {
ScopedWlArray states;
// The surface must react on each configure event and send bounds to its
// delegate.
constexpr gfx::Size kSize{1000, 1000};
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 recieved by
// xdg_toplevel in xdg_shell_v6 and by xdg_surface_ in xdg_shell_v5.
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(kSize))).Times(1);
EXPECT_CALL(*xdg_surface_, AckConfigure(12));
SendConfigureEvent(xdg_surface_, kSize, 12, states.get());
Sync();
constexpr gfx::Size kNewSize{1500, 1000};
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(kNewSize))).Times(1);
EXPECT_CALL(*xdg_surface_, AckConfigure(13));
SendConfigureEvent(xdg_surface_, kNewSize, 13, states.get());
}
TEST_P(WaylandWindowTest, ConfigureEventWithNulledSize) {
ScopedWlArray states;
// 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(xdg_surface_, {0, 0}, 14, states.get());
// |xdg_surface_| must receive the following calls in both xdg_shell_v5 and
// xdg_shell_v6. Other calls like SetTitle or SetMaximized are recieved by
// xdg_toplevel in xdg_shell_v6 and by xdg_surface_ in xdg_shell_v5.
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(800, 600)));
EXPECT_CALL(*xdg_surface_, AckConfigure(14));
}
TEST_P(WaylandWindowTest, OnActivationChanged) {
uint32_t serial = 0;
// Deactivate the surface.
ScopedWlArray empty_state;
SendConfigureEvent(xdg_surface_, {0, 0}, ++serial, empty_state.get());
Sync();
{
ScopedWlArray states = InitializeWlArrayWithActivatedState();
EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
SendConfigureEvent(xdg_surface_, {0, 0}, ++serial, states.get());
Sync();
}
ScopedWlArray states;
EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
SendConfigureEvent(xdg_surface_, {0, 0}, ++serial, states.get());
Sync();
}
TEST_P(WaylandWindowTest, OnAcceleratedWidgetDestroy) {
window_.reset();
}
TEST_P(WaylandWindowTest, CanCreateMenuWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillRepeatedly(Return(MenuType::kRootContextMenu));
// SetPointerFocus(true) requires a WaylandPointer.
wl_seat_send_capabilities(
server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH);
Sync();
ASSERT_TRUE(connection_->seat()->pointer() && connection_->seat()->touch());
SetPointerFocusedWindow(window_.get());
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, gfx::Rect(10, 10),
&menu_window_delegate);
EXPECT_TRUE(menu_window);
Sync();
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::kNullAcceleratedWidget, gfx::Rect(10, 10),
&menu_window_delegate);
EXPECT_TRUE(menu_window);
Sync();
window_->set_touch_focus(true);
menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, gfx::Rect(10, 10),
&menu_window_delegate);
EXPECT_TRUE(menu_window);
Sync();
}
TEST_P(WaylandWindowTest, CreateAndDestroyNestedMenuWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate;
gfx::AcceleratedWidget menu_window_widget;
EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_))
.WillOnce(SaveArg<0>(&menu_window_widget));
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillRepeatedly(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> menu_window =
CreateWaylandWindowWithParams(PlatformWindowType::kMenu, widget_,
gfx::Rect(10, 10), &menu_window_delegate);
EXPECT_TRUE(menu_window);
ASSERT_NE(menu_window_widget, gfx::kNullAcceleratedWidget);
Sync();
MockWaylandPlatformWindowDelegate nested_menu_window_delegate;
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_widget,
gfx::Rect(20, 0, 10, 10), &nested_menu_window_delegate);
EXPECT_TRUE(nested_menu_window);
Sync();
}
TEST_P(WaylandWindowTest, DispatchesLocatedEventsToCapturedWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, widget_, gfx::Rect(10, 10, 10, 10),
&menu_window_delegate);
EXPECT_TRUE(menu_window);
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
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));
// The event is send in local surface coordinates of the |window|.
wl_pointer_send_motion(server_.seat()->pointer()->resource(), 1002,
wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server_.seat()->pointer()->resource());
Sync();
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));
// The event is send in local surface coordinates of the |window|.
wl_pointer_send_motion(server_.seat()->pointer()->resource(), 1002,
wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server_.seat()->pointer()->resource());
Sync();
ASSERT_TRUE(event2->IsLocatedEvent());
EXPECT_EQ(event2->AsLocatedEvent()->location(), gfx::Point(0, 10));
// The event is send in local surface coordinates of the |window|.
wl_pointer_send_motion(server_.seat()->pointer()->resource(), 1002,
wl_fixed_from_double(2.75),
wl_fixed_from_double(8.375));
wl_pointer_send_frame(server_.seat()->pointer()->resource());
EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
std::unique_ptr<Event> event3;
EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
.WillOnce(CloneEvent(&event3));
Sync();
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;
EXPECT_CALL(nested_menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window->GetWidget(),
gfx::Rect(15, 18, 10, 10), &nested_menu_window_delegate);
EXPECT_TRUE(nested_menu_window);
Sync();
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));
// The event is send in local surface coordinates of the |nested_menu_window|.
wl_pointer_send_motion(server_.seat()->pointer()->resource(), 1002,
wl_fixed_from_double(2.75),
wl_fixed_from_double(8.375));
wl_pointer_send_frame(server_.seat()->pointer()->resource());
Sync();
ASSERT_TRUE(event4->IsLocatedEvent());
EXPECT_EQ(event4->AsLocatedEvent()->location(), gfx::Point(7, 16));
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) {
// This first section repeats a part of SetDecorationInsets that sets
// decoration insets and ensures that they have been applied.
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(_)).Times(0);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(bounds_with_insets));
window_->SetDecorationInsets(&kMainWindowInsets);
// Setting the decoration insets does not trigger the immediate update of the
// window geometry. Emulate updating the visual size (sending the frame
// update) for that.
window_->UpdateVisualSize(kMainWindowBounds.size());
Sync();
// Create a menu.
constexpr gfx::Rect kMenuBounds{100, 100, 80, 50};
MockWaylandPlatformWindowDelegate menu_window_delegate;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, widget_, kMenuBounds, &menu_window_delegate);
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 parent geometry + offset of the menu).
constexpr gfx::PointF kParentPoint{0, 0};
ui::MouseEvent event(ui::EventType::ET_MOUSE_MOVED, 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() + kMainWindowInsets.left()));
EXPECT_EQ(event.AsLocatedEvent()->y(),
-(kMenuBounds.y() + kMainWindowInsets.top()));
}
// 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;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, widget_, gfx::Rect(30, 40, 20, 50),
&menu_window_delegate);
EXPECT_TRUE(menu_window);
// Second toplevel window has the same bounds as the |window_|.
MockWaylandPlatformWindowDelegate toplevel_window2_delegate;
std::unique_ptr<WaylandWindow> toplevel_window2 =
CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget,
window_->GetBoundsInDIP(), &toplevel_window2_delegate);
EXPECT_TRUE(toplevel_window2);
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
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));
// The event is send in local surface coordinates of the |window|.
wl_pointer_send_motion(server_.seat()->pointer()->resource(), 1002,
wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server_.seat()->pointer()->resource());
Sync();
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));
// The event is send 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(), 1002,
wl_fixed_from_double(10.75),
wl_fixed_from_double(20.375));
wl_pointer_send_frame(server_.seat()->pointer()->resource());
Sync();
ASSERT_TRUE(event->IsLocatedEvent());
EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 20));
}
TEST_P(WaylandWindowTest, DispatchesKeyboardEventToToplevelWindow) {
MockWaylandPlatformWindowDelegate menu_window_delegate;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, widget_, gfx::Rect(10, 10, 10, 10),
&menu_window_delegate);
EXPECT_TRUE(menu_window);
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_KEYBOARD);
Sync();
ASSERT_TRUE(connection_->seat()->keyboard());
SetKeyboardFocusedWindow(menu_window.get());
// 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));
wl_keyboard_send_key(server_.seat()->keyboard()->resource(), 2, 0, 30 /* a */,
WL_KEYBOARD_KEY_STATE_PRESSED);
Sync();
ASSERT_TRUE(event->IsKeyEvent());
// Setting capture doesn't affect the kbd events.
menu_window->SetCapture();
VerifyCanDispatchKeyEvents({menu_window.get()}, {window_.get()});
wl_keyboard_send_key(server_.seat()->keyboard()->resource(), 2, 0, 30 /* a */,
WL_KEYBOARD_KEY_STATE_PRESSED);
EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
event.reset();
EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
Sync();
ASSERT_TRUE(event->IsKeyEvent());
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;
gfx::AcceleratedWidget menu_window_widget;
EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_))
.WillOnce(SaveArg<0>(&menu_window_widget));
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> menu_window =
CreateWaylandWindowWithParams(PlatformWindowType::kMenu, widget_,
gfx::Rect(10, 10), &menu_window_delegate);
EXPECT_TRUE(menu_window);
Sync();
MockWaylandPlatformWindowDelegate nested_menu_window_delegate;
EXPECT_CALL(nested_menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
std::unique_ptr<WaylandWindow> nested_menu_window =
CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, menu_window_widget,
gfx::Rect(20, 0, 10, 10), &nested_menu_window_delegate);
EXPECT_TRUE(nested_menu_window);
Sync();
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER |
WL_SEAT_CAPABILITY_KEYBOARD |
WL_SEAT_CAPABILITY_TOUCH);
Sync();
ASSERT_TRUE(connection_->seat()->pointer());
ASSERT_TRUE(connection_->seat()->touch());
ASSERT_TRUE(connection_->seat()->keyboard());
uint32_t serial = 0;
// Test that CanDispatchEvent is set correctly.
wl::MockSurface* toplevel_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
wl_pointer_send_enter(server_.seat()->pointer()->resource(), ++serial,
toplevel_surface->resource(), 0, 0);
Sync();
// 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()});
struct wl_array empty;
wl_array_init(&empty);
wl_keyboard_send_enter(server_.seat()->keyboard()->resource(), ++serial,
toplevel_surface->resource(), &empty);
Sync();
// 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()});
wl_touch_send_down(server_.seat()->touch()->resource(), ++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());
Sync();
// 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()});
wl::MockSurface* menu_window_surface = server_.GetObject<wl::MockSurface>(
menu_window->root_surface()->GetSurfaceId());
wl_pointer_send_leave(server_.seat()->pointer()->resource(), ++serial,
toplevel_surface->resource());
wl_pointer_send_enter(server_.seat()->pointer()->resource(), ++serial,
menu_window_surface->resource(), 0, 0);
wl_touch_send_up(server_.seat()->touch()->resource(), ++serial, 1000,
0 /* id */);
wl_touch_send_frame(server_.seat()->touch()->resource());
wl_keyboard_send_leave(server_.seat()->keyboard()->resource(), ++serial,
toplevel_surface->resource());
Sync();
// 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()});
wl::MockSurface* nested_menu_window_surface =
server_.GetObject<wl::MockSurface>(
nested_menu_window->root_surface()->GetSurfaceId());
wl_pointer_send_leave(server_.seat()->pointer()->resource(), ++serial,
menu_window_surface->resource());
wl_pointer_send_enter(server_.seat()->pointer()->resource(), ++serial,
nested_menu_window_surface->resource(), 0, 0);
Sync();
// 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) {
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
ASSERT_TRUE(connection_->seat()->pointer());
// 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, 1, surface_->resource(), 0, 0);
wl_pointer_send_button(pointer_resource, 2, 1, BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
Sync();
EXPECT_CALL(*GetXdgToplevel(), 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);
// Focus and press left mouse button, so that serial is sent to client.
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
ASSERT_TRUE(connection_->seat()->pointer());
auto* pointer_resource = server_.seat()->pointer()->resource();
wl_pointer_send_enter(pointer_resource, 1, surface_->resource(), 0, 0);
wl_pointer_send_button(pointer_resource, 2, 1, BTN_LEFT,
WL_POINTER_BUTTON_STATE_PRESSED);
Sync();
auto* wm_move_resize_handler = ui::GetWmMoveResizeHandler(*window_);
for (const int value : hit_test_values) {
{
uint32_t direction = wl::IdentifyDirection(*(connection_.get()), value);
EXPECT_CALL(*GetXdgToplevel(), 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_->window_scale());
// Creating an output with scale 1.
wl::TestOutput* output1 = server_.CreateAndInitializeOutput();
output1->SetRect(gfx::Rect(1920, 1080));
output1->SetScale(1);
Sync();
// Creating an output with scale 2.
wl::TestOutput* output2 = server_.CreateAndInitializeOutput();
output2->SetRect(gfx::Rect(1920, 1080));
output2->SetScale(2);
Sync();
// Send the window to |output1|.
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), output1->resource());
Sync();
// The window's scale and bounds must remain unchanged.
EXPECT_EQ(1, window_->window_scale());
EXPECT_EQ(gfx::Size(800, 600), window_->size_px());
EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
// Simulating drag process from |output1| to |output2|.
wl_surface_send_enter(surface->resource(), output2->resource());
wl_surface_send_leave(surface->resource(), output1->resource());
Sync();
// The window must change its scale and bounds to keep DIP bounds the same.
EXPECT_EQ(2, window_->window_scale());
EXPECT_EQ(gfx::Size(1600, 1200), window_->size_px());
EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
}
TEST_P(WaylandWindowTest, WaylandPopupSurfaceScale) {
VerifyAndClearExpectations();
// Creating an output with scale 1.
wl::TestOutput* output1 = server_.CreateAndInitializeOutput();
output1->SetRect(gfx::Rect(1920, 1080));
output1->SetScale(1);
Sync();
// Creating an output with scale 2.
wl::TestOutput* output2 = server_.CreateAndInitializeOutput();
output2->SetRect(gfx::Rect(1920, 0, 1920, 1080));
output2->SetScale(2);
Sync();
std::vector<PlatformWindowType> window_types{PlatformWindowType::kMenu,
PlatformWindowType::kTooltip};
for (const auto& type : window_types) {
// Send the window to |output1|.
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), output1->resource());
Sync();
// Creating a wayland_popup on |window_|.
SetPointerFocusedWindow(window_.get());
gfx::Rect wayland_popup_bounds(15, 15, 10, 10);
auto wayland_popup = CreateWaylandWindowWithParams(
type, window_->GetWidget(), wayland_popup_bounds, &delegate_);
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_->window_scale());
EXPECT_EQ(window_->window_scale(), wayland_popup->window_scale());
EXPECT_EQ(wayland_popup_bounds.size(), wayland_popup->size_px());
EXPECT_EQ(wayland_popup_bounds, wayland_popup->GetBoundsInDIP());
wayland_popup->Hide();
// Send the window to |output2|.
wl_surface_send_enter(surface->resource(), output2->resource());
wl_surface_send_leave(surface->resource(), output1->resource());
Sync();
EXPECT_EQ(2, window_->window_scale());
wayland_popup->Show(false);
Sync();
// |wayland_popup|'s scale and bounds must change whenever its parents
// scale is changed.
EXPECT_EQ(window_->window_scale(), wayland_popup->window_scale());
EXPECT_EQ(gfx::ScaleToCeiledSize(wayland_popup_bounds.size(),
wayland_popup->window_scale()),
wayland_popup->size_px());
wayland_popup->Hide();
SetPointerFocusedWindow(nullptr);
wl_surface_send_leave(surface->resource(), output2->resource());
Sync();
}
}
// 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();
wl::TestOutput* main_output = server_.CreateAndInitializeOutput();
main_output->SetRect(gfx::Rect(1920, 1080));
main_output->SetScale(1);
Sync();
wl::TestOutput* secondary_output = server_.CreateAndInitializeOutput();
secondary_output->SetRect(gfx::Rect(1921, 0, 1920, 1080));
secondary_output->SetScale(1);
Sync();
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
struct {
raw_ptr<wl::TestOutput> output;
const char* label;
} screen[] = {{main_output, "main output"},
{secondary_output, "secondary output"}};
for (const auto& entered_output : screen) {
wl_surface_send_enter(surface->resource(),
entered_output.output->resource());
Sync();
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++) {
// Update scale factors first.
main_output->SetScale(main_output_scale);
secondary_output->SetScale(secondary_output_scale);
main_output->Flush();
secondary_output->Flush();
Sync();
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->GetScale();
gfx::Transform transform;
transform.Scale(effective_scale, effective_scale);
gfx::RectF rect_in_pixels = gfx::RectF(bounds_dip);
transform.TransformRect(&rect_in_pixels);
gfx::Rect wayland_popup_bounds = gfx::ToEnclosingRect(rect_in_pixels);
std::unique_ptr<WaylandWindow> wayland_popup =
CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
window_->GetWidget(), bounds_dip,
&delegate_);
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->size_px())
<< " when the window is on " << entered_output.label
<< " that has scale " << entered_output.output->GetScale();
}
}
wl_surface_send_leave(surface->resource(),
entered_output.output->resource());
Sync();
}
}
TEST_P(WaylandWindowTest, WaylandPopupInitialBufferUsesParentScale) {
VerifyAndClearExpectations();
// Creating an output with scale 1.
wl::TestOutput* main_output = server_.CreateAndInitializeOutput();
main_output->SetRect(gfx::Rect(1920, 1080));
main_output->SetScale(1);
Sync();
// Creating an output with scale 2.
wl::TestOutput* secondary_output = server_.CreateAndInitializeOutput();
secondary_output->SetRect(gfx::Rect(1921, 0, 1920, 1080));
secondary_output->SetScale(2);
Sync();
// Send the window to |output1|.
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), secondary_output->resource());
Sync();
constexpr gfx::Rect kBoundsDip{50, 50, 100, 100};
const gfx::Size expected_size_px =
gfx::ScaleToCeiledSize(kBoundsDip.size(), secondary_output->GetScale());
std::unique_ptr<WaylandWindow> wayland_popup = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, window_->GetWidget(), kBoundsDip, &delegate_);
EXPECT_TRUE(wayland_popup);
wayland_popup->Show(false);
EXPECT_EQ(expected_size_px, wayland_popup->size_px());
wl_surface_send_leave(surface->resource(), secondary_output->resource());
Sync();
}
// 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_->window_scale());
// Creating an output with scale 1.
wl::TestOutput* output1 = server_.CreateAndInitializeOutput();
output1->SetRect(gfx::Rect(1920, 1080));
Sync();
// Creating an output with scale 2.
wl::TestOutput* output2 = server_.CreateAndInitializeOutput();
output2->SetRect(gfx::Rect(1921, 0, 1920, 1080));
Sync();
auto entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(0u, entered_outputs.size());
// Send the window to |output1|.
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), output1->resource());
wl_surface_send_enter(surface->resource(), output2->resource());
Sync();
// The window entered two outputs.
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(2u, entered_outputs.size());
// The window must prefer the output that it entered first.
uint32_t expected_entered_output_id = *entered_outputs.begin();
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
// Create the third output and pretend the window entered 3 outputs at the
// same time.
wl::TestOutput* output3 = server_.CreateAndInitializeOutput();
output3->SetRect(gfx::Rect(0, 1081, 1920, 1080));
Sync();
wl_surface_send_enter(surface->resource(), output3->resource());
Sync();
// The window entered three outputs...
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(3u, entered_outputs.size());
// but it still must prefer the output that it entered first.
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
// Pretend that the output2 has scale factor equals to 2 now.
output2->SetScale(2);
output2->Flush();
Sync();
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(3u, entered_outputs.size());
// It must be the second entered output now.
expected_entered_output_id = *(++entered_outputs.begin());
auto* expected_entered_output =
connection_->wayland_output_manager()->GetOutput(
expected_entered_output_id);
EXPECT_EQ(2, expected_entered_output->scale_factor());
// The window_ must return the output with largest scale.
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
// Now, the output1 changes its scale factor to 2 as well.
output1->SetScale(2);
output1->Flush();
Sync();
// It must be the very first output now.
entered_outputs = window_->root_surface()->entered_outputs();
expected_entered_output_id = entered_outputs.front();
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
// Now, the output1 changes its scale factor back to 1.
output1->SetScale(1);
output1->Flush();
Sync();
// It must be the very the second output now.
entered_outputs = window_->root_surface()->entered_outputs();
expected_entered_output_id = *(++entered_outputs.begin());
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
// All outputs have scale factor of 1. window_ prefers the output that
// it entered first again.
output2->SetScale(1);
output2->Flush();
Sync();
entered_outputs = window_->root_surface()->entered_outputs();
expected_entered_output_id = entered_outputs.front();
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
}
TEST_P(WaylandWindowTest, GetChildrenPreferredOutput) {
VerifyAndClearExpectations();
// Buffer scale must be 1 when no output has been entered by the window.
EXPECT_EQ(1, window_->window_scale());
MockWaylandPlatformWindowDelegate menu_window_delegate;
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, window_->GetWidget(),
gfx::Rect(10, 10, 10, 10), &menu_window_delegate);
menu_window->Show(false);
Sync();
// Creating an output with scale 1.
wl::TestOutput* output1 = server_.CreateAndInitializeOutput();
output1->SetRect(gfx::Rect(1920, 1080));
Sync();
// Creating an output with scale 2.
wl::TestOutput* output2 = server_.CreateAndInitializeOutput();
output2->SetRect(gfx::Rect(1921, 0, 1920, 1080));
Sync();
auto entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(0u, entered_outputs.size());
auto menu_entered_outputs = menu_window->root_surface()->entered_outputs();
EXPECT_EQ(0u, menu_entered_outputs.size());
// Enter |output1|.
wl::MockSurface* surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), output1->resource());
Sync();
// The window entered the output.
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(1u, entered_outputs.size());
// The menu also thinks it entered the same output.
menu_entered_outputs = menu_window->root_surface()->entered_outputs();
EXPECT_EQ(0u, menu_entered_outputs.size());
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
// Pretend Wayland sends that menu entered output2, while the toplevel is on
// output1. Output1 must still be preferred by the menu.
wl::MockSurface* menu_surface = server_.GetObject<wl::MockSurface>(
menu_window->root_surface()->GetSurfaceId());
wl_surface_send_enter(menu_surface->resource(), output1->resource());
Sync();
// The menu surface should be aware of the output that Wayland sent it.
EXPECT_EQ(1u, window_->root_surface()->entered_outputs().size());
EXPECT_EQ(1u, menu_window->root_surface()->entered_outputs().size());
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
// Pretend Wayland sends that toplevel entered output2.
wl_surface_send_enter(surface->resource(), output1->resource());
Sync();
EXPECT_EQ(2u, window_->root_surface()->entered_outputs().size());
EXPECT_EQ(1u, menu_window->root_surface()->entered_outputs().size());
EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
menu_window->GetPreferredEnteredOutputId());
// Now, the output2 changes its scale factor to 2.
output2->SetScale(2);
output2->Flush();
Sync();
// It must be the very the second output now.
entered_outputs = window_->root_surface()->entered_outputs();
uint32_t expected_entered_output_id = *(++entered_outputs.begin());
EXPECT_TRUE(window_->GetPreferredEnteredOutputId());
EXPECT_EQ(expected_entered_output_id,
*window_->GetPreferredEnteredOutputId());
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;
if (GetParam().shell_version == wl::ShellVersion::kV6) {
menu_window_positioner = {gfx::Rect(439, 46, 1, 1), gfx::Size(287, 409),
ZXDG_POSITIONER_V6_ANCHOR_TOP |
ZXDG_POSITIONER_V6_ANCHOR_LEFT,
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_RIGHT,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y},
nested_menu_window_positioner = {
gfx::Rect(285, 1, 1, 1), gfx::Size(305, 99),
ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT,
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y};
} else {
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;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootMenu));
EXPECT_CALL(menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
.WillOnce(Return(absl::nullopt));
gfx::Rect menu_window_bounds(gfx::Point(439, 46),
menu_window_positioner.size);
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, toplevel_window->GetWidget(),
menu_window_bounds, &menu_window_delegate);
EXPECT_TRUE(menu_window);
Sync();
VerifyXdgPopupPosition(menu_window.get(), menu_window_positioner);
EXPECT_CALL(menu_window_delegate, OnBoundsChanged(_)).Times(0);
SendConfigureEventPopup(menu_window.get(), menu_window_bounds);
Sync();
EXPECT_EQ(menu_window->GetBoundsInDIP(), menu_window_bounds);
// Case 2: the nested menu window is positioned normally.
MockWaylandPlatformWindowDelegate nested_menu_window_delegate;
EXPECT_CALL(nested_menu_window_delegate, GetMenuType())
.WillRepeatedly(Return(MenuType::kChildMenu));
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, menu_window->GetWidget(),
nested_menu_window_bounds, &nested_menu_window_delegate);
EXPECT_TRUE(nested_menu_window);
Sync();
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;
if (GetParam().shell_version == wl::ShellVersion::kV6) {
menu_window_positioner = {gfx::Rect(468, 46, 28, 28), gfx::Size(320, 404),
ZXDG_POSITIONER_V6_ANCHOR_BOTTOM |
ZXDG_POSITIONER_V6_ANCHOR_RIGHT,
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM |
ZXDG_POSITIONER_V6_GRAVITY_LEFT,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y},
nested_menu_window_positioner = {
gfx::Rect(4, 83, 312, 1), gfx::Size(480, 294),
ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_RIGHT,
ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT,
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y |
ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_X};
} else {
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;
EXPECT_CALL(menu_window_delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootMenu));
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, toplevel_window->GetWidget(),
menu_window_bounds, &menu_window_delegate);
EXPECT_TRUE(menu_window);
Sync();
VerifyXdgPopupPosition(menu_window.get(), menu_window_positioner);
Sync();
MockWaylandPlatformWindowDelegate nested_menu_window_delegate;
EXPECT_CALL(nested_menu_window_delegate, GetMenuType())
.WillRepeatedly(Return(MenuType::kChildMenu));
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, menu_window->GetWidget(),
nested_menu_window_bounds, &nested_menu_window_delegate);
EXPECT_TRUE(nested_menu_window);
Sync();
VerifyXdgPopupPosition(nested_menu_window.get(),
nested_menu_window_positioner);
}
ACTION_P(VerifyRegion, ptr) {
wl::TestRegion* region = wl::GetUserDataAs<wl::TestRegion>(arg0);
EXPECT_EQ(*ptr, region->getBounds());
}
TEST_P(WaylandWindowTest, SetOpaqueRegion) {
wl::MockSurface* mock_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
gfx::Rect new_bounds(500, 600);
auto state_array = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED});
SendConfigureEvent(xdg_surface_, new_bounds.size(), 1, state_array.get());
SkIRect rect =
SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height());
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).WillOnce(VerifyRegion(&rect));
Sync();
VerifyAndClearExpectations();
EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
new_bounds.set_size(gfx::Size(1000, 534));
SendConfigureEvent(xdg_surface_, new_bounds.size(), 2, state_array.get());
rect = SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height());
EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).WillOnce(VerifyRegion(&rect));
Sync();
VerifyAndClearExpectations();
EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
}
TEST_P(WaylandWindowTest, OnCloseRequest) {
EXPECT_CALL(delegate_, OnCloseRequest());
if (GetParam().shell_version == wl::ShellVersion::kV6)
zxdg_toplevel_v6_send_close(xdg_surface_->xdg_toplevel()->resource());
else
xdg_toplevel_send_close(xdg_surface_->xdg_toplevel()->resource());
Sync();
}
TEST_P(WaylandWindowTest, WaylandPopupSimpleParent) {
VerifyAndClearExpectations();
EXPECT_CALL(delegate_, GetMenuType())
.WillRepeatedly(Return(MenuType::kRootContextMenu));
// 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, window_->GetWidget(), wayland_popup_bounds,
&delegate_);
EXPECT_TRUE(wayland_popup);
wayland_popup->Show(false);
Sync();
auto* mock_surface_popup = server_.GetObject<wl::MockSurface>(
wayland_popup->root_surface()->GetSurfaceId());
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, window_->GetWidget(), menu_window_bounds,
&delegate_);
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, menu_window->GetWidget(),
nested_wayland_popup_bounds, &delegate_);
EXPECT_TRUE(nested_wayland_popup);
VerifyAndClearExpectations();
nested_wayland_popup->Show(false);
Sync();
auto* mock_surface_nested = server_.GetObject<wl::MockSurface>(
nested_wayland_popup->root_surface()->GetSurfaceId());
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});
Sync();
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);
Sync();
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);
Sync();
}
// 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});
Sync();
uint32_t serial = 0;
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(xdg_surface_, even_smaller_bounds.size(), ++serial,
state.get());
Sync();
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(xdg_surface_, even_greater_bounds.size(), ++serial,
state.get());
Sync();
}
TEST_P(WaylandWindowTest, OnSizeConstraintsChanged) {
const bool kBooleans[] = {false, true};
for (bool has_min_size : kBooleans) {
for (bool has_max_size : kBooleans) {
absl::optional<gfx::Size> min_size =
has_min_size ? absl::optional<gfx::Size>(gfx::Size(100, 200))
: absl::nullopt;
absl::optional<gfx::Size> max_size =
has_max_size ? absl::optional<gfx::Size>(gfx::Size(300, 400))
: absl::nullopt;
EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
.WillOnce(Return(min_size));
EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
.WillOnce(Return(max_size));
EXPECT_CALL(*GetXdgToplevel(), SetMinSize(100, 200))
.Times(has_min_size ? 1 : 0);
EXPECT_CALL(*GetXdgToplevel(), SetMaxSize(300, 400))
.Times(has_max_size ? 1 : 0);
window_->SizeConstraintsChanged();
Sync();
VerifyAndClearExpectations();
}
}
}
TEST_P(WaylandWindowTest, DestroysCreatesSurfaceOnHideShow) {
MockWaylandPlatformWindowDelegate delegate;
auto window = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
gfx::kNullAcceleratedWidget,
gfx::Rect(100, 100), &delegate);
ASSERT_TRUE(window);
Sync();
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
Sync();
window->Hide();
Sync();
EXPECT_FALSE(mock_surface->xdg_surface());
window->Show(false);
Sync();
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
}
TEST_P(WaylandWindowTest, DestroysCreatesPopupsOnHideShow) {
MockWaylandPlatformWindowDelegate delegate;
EXPECT_CALL(delegate, GetMenuType())
.WillRepeatedly(Return(MenuType::kRootContextMenu));
auto window = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
window_->GetWidget(),
gfx::Rect(50, 50), &delegate);
ASSERT_TRUE(window);
Sync();
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_popup());
Sync();
window->Hide();
Sync();
EXPECT_FALSE(mock_surface->xdg_surface());
window->Show(false);
Sync();
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), {}, false, true,
false, kAugmentedSurfaceNotSupportedVersion);
// 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);
Sync();
// Create window.
MockWaylandPlatformWindowDelegate delegate;
auto window = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
gfx::kNullAcceleratedWidget,
gfx::Rect(100, 100), &delegate);
ASSERT_TRUE(window);
auto states = InitializeWlArrayWithActivatedState();
Sync();
// Configure window to be ready to attach wl_buffers.
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
EXPECT_TRUE(mock_surface->xdg_surface());
EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
SendConfigureEvent(mock_surface->xdg_surface(), {100, 100}, 1, states.get());
// 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,
std::move(overlays));
mock_surface->SendFrameCallback();
Sync();
EXPECT_NE(mock_surface->attached_buffer(), nullptr);
window->Hide();
mock_surface->SendFrameCallback();
Sync();
mock_surface->ReleaseBuffer(mock_surface->attached_buffer());
window->Show(false);
Sync();
SendConfigureEvent(mock_surface->xdg_surface(), {100, 100}, 2, states.get());
// 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,
std::move(overlays));
Sync();
// 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;
auto window = delegate.CreateWaylandWindow(connection_.get(),
std::move(properties), true, true);
DCHECK(window);
window->Show(false);
Sync();
auto* mock_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
// Only app id must be set now.
EXPECT_EQ(window->GetWindowUniqueId(), 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.
absl::optional<gfx::Size> min_size(gfx::Size(1, 1));
absl::optional<gfx::Size> max_size(gfx::Size(100, 100));
EXPECT_CALL(delegate, GetMinimumSizeForWindow())
.Times(2)
.WillRepeatedly(Return(min_size));
EXPECT_CALL(delegate, GetMaximumSizeForWindow())
.Times(2)
.WillRepeatedly(Return(max_size));
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();
Sync();
window->Hide();
Sync();
window->Show(false);
Sync();
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(window->GetWindowUniqueId(), 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);
}
wl_seat_send_capabilities(
server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD);
Sync();
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;
wl::MockSurface* toplevel_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
struct wl_array empty;
wl_array_init(&empty);
wl_keyboard_send_enter(server_.seat()->keyboard()->resource(),
keyboard_enter_serial, toplevel_surface->resource(),
&empty);
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);
Sync();
// Create a popup window and verify the client used correct serial.
MockWaylandPlatformWindowDelegate delegate;
EXPECT_CALL(delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
window_->GetWidget(),
gfx::Rect(50, 50), &delegate);
ASSERT_TRUE(popup);
Sync();
auto* test_popup = GetTestXdgPopupByWindow(popup.get());
ASSERT_TRUE(test_popup);
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
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);
}
#else
// crbug.com/1320528: Lacros uses explicit grab always.
EXPECT_NE(test_popup->grab_serial(), button_release_serial);
EXPECT_EQ(test_popup->grab_serial(), button_press_serial);
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
}
}
// 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);
}
wl_seat_send_capabilities(
server_.seat()->resource(),
WL_SEAT_CAPABILITY_TOUCH | WL_SEAT_CAPABILITY_KEYBOARD);
Sync();
constexpr uint32_t enter_serial = 1;
constexpr uint32_t touch_down_serial = 2;
constexpr uint32_t touch_up_serial = 3;
wl::MockSurface* toplevel_surface = server_.GetObject<wl::MockSurface>(
window_->root_surface()->GetSurfaceId());
struct wl_array empty;
wl_array_init(&empty);
wl_keyboard_send_enter(server_.seat()->keyboard()->resource(), enter_serial,
toplevel_surface->resource(), &empty);
// Send two events - touch down and touch up.
wl_touch_send_down(server_.seat()->touch()->resource(), touch_down_serial,
0, 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());
Sync();
// Create a popup window and verify the client used correct serial.
MockWaylandPlatformWindowDelegate delegate;
EXPECT_CALL(delegate, GetMenuType())
.WillRepeatedly(Return(MenuType::kRootContextMenu));
auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
window_->GetWidget(),
gfx::Rect(50, 50), &delegate);
ASSERT_TRUE(popup);
Sync();
auto* test_popup = GetTestXdgPopupByWindow(popup.get());
ASSERT_TRUE(test_popup);
// crbug.com/1320528: Lacros uses explicit grab always.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
// 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);
#endif
popup->Hide();
// Send a single down event now.
wl_touch_send_down(server_.seat()->touch()->resource(), touch_down_serial,
0, surface_->resource(), 0 /* id */,
wl_fixed_from_int(50), wl_fixed_from_int(100));
wl_touch_send_frame(server_.seat()->touch()->resource());
Sync();
popup->Show(false);
Sync();
test_popup = GetTestXdgPopupByWindow(popup.get());
ASSERT_TRUE(test_popup);
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
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);
}
#else
// crbug.com/1320528: Lacros uses explicit grab always.
EXPECT_EQ(test_popup->grab_serial(), touch_down_serial);
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
}
}
// 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, window_->GetWidget(), menu_window_bounds,
&delegate_);
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->GetWidget(), menu_window_bounds2,
&delegate_);
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_window2->GetWidget(), menu_window_bounds3,
&delegate_);
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_window3->GetWidget(), menu_window_bounds4,
&delegate_);
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;
EXPECT_CALL(delegate, GetMenuType())
.WillOnce(Return(MenuType::kRootContextMenu));
auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
window_->GetWidget(),
gfx::Rect(50, 50), &delegate);
ASSERT_TRUE(popup);
Sync();
auto* test_popup = GetTestXdgPopupByWindow(popup.get());
ASSERT_TRUE(test_popup);
EXPECT_EQ(test_popup->grab_serial(), 0u);
}
// Regression test for https://crbug.com/1247799.
TEST_P(WaylandWindowTest, DoesNotGrabPopupUnlessParentHasGrab) {
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
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;
std::unique_ptr<WaylandWindow> root_menu;
EXPECT_CALL(delegate, GetMenuType()).WillOnce(Return(MenuType::kRootMenu));
root_menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
window_->GetWidget(),
gfx::Rect(50, 50), &delegate);
Sync();
VerifyAndClearExpectations();
Mock::VerifyAndClearExpectations(&delegate);
ASSERT_TRUE(root_menu);
auto* server_root_menu = GetTestXdgPopupByWindow(root_menu.get());
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.
auto* server_root_menu_surface = server_.GetObject<wl::MockSurface>(
root_menu->root_surface()->GetSurfaceId());
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);
EXPECT_CALL(delegate, DispatchEvent(_)).Times(2);
Sync();
Mock::VerifyAndClearExpectations(&delegate);
MockWaylandPlatformWindowDelegate delegate_2;
std::unique_ptr<WaylandWindow> child_menu;
EXPECT_CALL(delegate_2, GetMenuType()).WillOnce(Return(MenuType::kChildMenu));
child_menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
root_menu->GetWidget(),
gfx::Rect(10, 10), &delegate_2);
Sync();
VerifyAndClearExpectations();
Mock::VerifyAndClearExpectations(&delegate_2);
ASSERT_TRUE(child_menu);
auto* server_child_menu = GetTestXdgPopupByWindow(child_menu.get());
ASSERT_TRUE(server_child_menu);
EXPECT_EQ(server_child_menu->grab_serial(), 0u);
wl_pointer_send_leave(pointer_resource, 5u /*serial*/,
server_root_menu_surface->resource());
wl_pointer_send_frame(pointer_resource);
Sync();
}
TEST_P(WaylandWindowTest, InitialBounds) {
testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate_2;
auto toplevel = CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, 0, gfx::Rect(10, 10, 200, 200), &delegate_2);
toplevel->HandleAuraToplevelConfigure(0, 0, 0, 0, {false, false, true});
toplevel->HandleSurfaceConfigure(2);
static_cast<WaylandToplevelWindow*>(toplevel.get())->ApplyPendingBounds();
EXPECT_EQ(gfx::Rect(10, 10, 200, 200), toplevel->GetBoundsInDIP());
}
TEST_P(WaylandWindowTest, PrimarySnappedState) {
testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate_2;
auto toplevel = CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, 0, gfx::Rect(0, 0, 200, 200), &delegate_2);
toplevel->HandleAuraToplevelConfigure(0, 0, 100, 200,
{.is_maximized = false,
.is_fullscreen = false,
.is_activated = true,
.is_snapped_primary = true});
toplevel->HandleSurfaceConfigure(2);
static_cast<WaylandToplevelWindow*>(toplevel.get())->ApplyPendingBounds();
EXPECT_EQ(gfx::Rect(0, 0, 100, 200), toplevel->GetBoundsInDIP());
}
TEST_P(WaylandWindowTest, SecondarySnappedState) {
testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate_2;
auto toplevel = CreateWaylandWindowWithParams(
PlatformWindowType::kWindow, 0, gfx::Rect(0, 0, 200, 200), &delegate_2);
toplevel->HandleAuraToplevelConfigure(100, 0, 100, 200,
{.is_maximized = false,
.is_fullscreen = false,
.is_activated = true,
.is_snapped_secondary = true});
toplevel->HandleSurfaceConfigure(2);
static_cast<WaylandToplevelWindow*>(toplevel.get())->ApplyPendingBounds();
EXPECT_EQ(gfx::Rect(100, 0, 100, 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::kNullAcceleratedWidget,
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();
Sync();
auto* mock_surface_root_window = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
auto* mock_surface_subsurface = server_.GetObject<wl::MockSurface>(
wayland_subsurface->wayland_surface()->GetSurfaceId());
EXPECT_TRUE(mock_surface_subsurface);
wayland_subsurface->ConfigureAndShowSurface(
subsurface_bounds, gfx::RectF(0, 0, 640, 480) /*parent_bounds_px*/,
absl::nullopt /*clip_rect_px*/, 1.f /*buffer_scale*/, nullptr, nullptr);
connection_->Flush();
Sync();
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 with and without surface
// augmenter.
TEST_P(WaylandSubsurfaceTest, OneWaylandSubsurfaceInteger) {
ASSERT_FALSE(connection_->surface_augmenter());
constexpr gfx::RectF test_data[2][2] = {
{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({7.041, 8.583}, {13.452, 20.231})}};
for (const auto& item : test_data) {
OneWaylandSubsurfaceTestHelper(item[0] /* subsurface_bounds */,
item[1] /* expected_subsurface_bounds */);
// Initialize the surface augmenter now.
InitializeSurfaceAugmenter();
ASSERT_TRUE(connection_->surface_augmenter());
};
}
TEST_P(WaylandSubsurfaceTest, OneWaylandSubsurfaceNonInteger) {
ASSERT_FALSE(connection_->surface_augmenter());
constexpr gfx::RectF test_data[2][2] = {
{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 */);
// Initialize the surface augmenter now.
InitializeSurfaceAugmenter();
ASSERT_TRUE(connection_->surface_augmenter());
}
}
TEST_P(WaylandSubsurfaceTest, NoDuplicateSubsurfaceRequests) {
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),
absl::nullopt, 1.f, nullptr, nullptr);
}
connection_->Flush();
Sync();
// From top to bottom: subsurfaces[2], subsurfaces[1], subsurfaces[0].
wl::TestSubSurface* test_subs[3] = {
server_
.GetObject<wl::MockSurface>(
subsurfaces[0]->wayland_surface()->GetSurfaceId())
->sub_surface(),
server_
.GetObject<wl::MockSurface>(
subsurfaces[1]->wayland_surface()->GetSurfaceId())
->sub_surface(),
server_
.GetObject<wl::MockSurface>(
subsurfaces[2]->wayland_surface()->GetSurfaceId())
->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),
absl::nullopt, 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),
absl::nullopt, 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),
absl::nullopt, 1.f, nullptr, subsurfaces[0]);
connection_->Flush();
Sync();
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), {}, false, true,
false, kAugmentedSurfaceNotSupportedVersion);
// 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);
Sync();
auto* surface = window_->root_surface();
auto* test_viewport =
server_.GetObject<wl::MockSurface>(surface->GetSurfaceId())->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->SetViewportSource({0.5, 0.5, 0.5, 0.5});
surface->SetViewportDestination({800, 600});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
Sync();
VerifyAndClearExpectations();
// 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->SetViewportSource({0.5, 0.5, 0.5, 0.5});
surface->SetViewportDestination({800, 600});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
Sync();
VerifyAndClearExpectations();
// 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->SetViewportSource({0., 0., 1., 1.});
surface->SetViewportDestination({1024, 768});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
Sync();
VerifyAndClearExpectations();
// 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->SetViewportSource({0., 0., 1., 1.});
surface->SetViewportDestination({1024, 768});
surface->ApplyPendingState();
surface->Commit();
connection_->Flush();
Sync();
VerifyAndClearExpectations();
}
// Tests that WaylandPopups can be repositioned.
TEST_P(WaylandWindowTest, RepositionPopups) {
VerifyAndClearExpectations();
EXPECT_CALL(delegate_, GetMenuType())
.WillRepeatedly(Return(MenuType::kRootContextMenu));
gfx::Rect menu_window_bounds(gfx::Rect(6, 20, 8, 20));
std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds,
&delegate_);
EXPECT_TRUE(menu_window);
EXPECT_TRUE(menu_window->IsVisible());
Sync();
auto* mock_surface_popup = server_.GetObject<wl::MockSurface>(
menu_window->root_surface()->GetSurfaceId());
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);
Sync();
// Xdg objects can be recreated depending on the version of the xdg shell.
mock_surface_popup = server_.GetObject<wl::MockSurface>(
menu_window->root_surface()->GetSurfaceId());
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(mock_surface_popup->xdg_surface(), {0, 0}, 1, nullptr);
// Call sync so that server's configuration event is received by
// Ozone/Wayland.
Sync();
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());
ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, {0, 0}, 1, states.get());
Sync();
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
window_->Minimize();
// 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);
ui::PlatformWindowState state;
EXPECT_CALL(delegate_, OnWindowStateChanged(_, _))
.WillRepeatedly(DoAll(SaveArg<0>(&state), InvokeWithoutArgs([&]() {
EXPECT_EQ(state, PlatformWindowState::kMinimized);
})));
// The window geometry has to be set to the current bounds of the window for
// minimized state.
gfx::Rect bounds = window_->GetBoundsInDIP();
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(bounds.size())));
// Send one additional empty configuration event for minimized state.
// (which means the surface is not maximized, fullscreen or activated)
states = ScopedWlArray();
SendConfigureEvent(xdg_surface_, {0, 0}, 2, states.get());
Sync();
}
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);
window->set_update_visual_size_immediately_for_testing(true);
window->set_apply_pending_state_on_update_visual_size_for_testing(true);
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() == ET_TOUCH_RELEASED && !blocked_) {
base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
blocked_ = true;
base::ThreadTaskRunnerHandle::Get()->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.
// The test is flaky. https://crbug.com/1305272.
TEST_P(WaylandWindowTest, DISABLED_BlockingTouchDownUp_NoCrash) {
window_.reset();
MockWaylandPlatformWindowDelegate delegate;
auto window = BlockableWaylandToplevelWindow::Create(
gfx::Rect(800, 600), connection_.get(), &delegate);
wl_seat_send_capabilities(
server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH);
Sync();
ASSERT_TRUE(connection_->seat()->pointer());
ASSERT_TRUE(connection_->seat()->touch());
window->set_touch_focus(true);
uint32_t serial = 0;
// Test that CanDispatchEvent is set correctly.
wl::MockSurface* toplevel_surface = server_.GetObject<wl::MockSurface>(
window->root_surface()->GetSurfaceId());
Sync();
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) {
wl_touch_send_down(server_.seat()->touch()->resource(), ++serial, 0,
toplevel_surface->resource(), 0 /* id */,
wl_fixed_from_int(100), wl_fixed_from_int(100));
wl_touch_send_up(server_.seat()->touch()->resource(), ++serial, 2000,
0 /* id */);
wl_touch_send_frame(server_.seat()->touch()->resource());
Sync();
std::move(closure).Run();
});
window->SetAsyncTask(std::move(async_task));
// Start executing the first touch down/up pair.
wl_touch_send_down(server_.seat()->touch()->resource(), ++serial, 0,
toplevel_surface->resource(), 0 /* id */,
wl_fixed_from_int(50), wl_fixed_from_int(50));
wl_touch_send_up(server_.seat()->touch()->resource(), ++serial, 1000,
0 /* id */);
wl_touch_send_frame(server_.seat()->touch()->resource());
Sync();
}
// 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
#if BUILDFLAG(IS_CHROMEOS_DEVICE)
#define MAYBE_ChangeFocusDuringDispatch DISABLED_ChangeFocusDuringDispatch
#else
#define MAYBE_ChangeFocusDuringDispatch ChangeFocusDuringDispatch
#endif
TEST_P(WaylandWindowTest, MAYBE_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);
wl_seat_send_capabilities(server_.seat()->resource(),
WL_SEAT_CAPABILITY_POINTER);
Sync();
wl::MockSurface* other_surface = server_.GetObject<wl::MockSurface>(
other_window->root_surface()->GetSurfaceId());
ASSERT_TRUE(other_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());
int count = 0;
EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) {
count++;
if (event->type() == ui::ET_MOUSE_PRESSED) {
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());
Sync();
}
});
EXPECT_CALL(other_delegate, DispatchEvent(_)).Times(1);
Sync();
EXPECT_EQ(count, 3);
}
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)));
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(0);
window_->SetBoundsInDIP(new_bounds);
// Resize and move.
new_bounds.Inset(5);
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange(true)))).Times(1);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(0);
window_->SetBoundsInDIP(new_bounds);
// Xdg configure event will reset the origin.
EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange(true)))).Times(1);
EXPECT_CALL(*xdg_surface_, SetWindowGeometry(gfx::Rect(new_bounds.size())))
.Times(1);
ScopedWlArray states = InitializeWlArrayWithActivatedState();
SendConfigureEvent(xdg_surface_, new_bounds.size(), 1, states.get());
Sync();
}
// 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;
std::unique_ptr<WaylandWindow> wayland_window =
CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
gfx::kNullAcceleratedWidget,
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(kBoundsDip);
wayland_window->SetBoundsInDIP(kBoundsDip);
EXPECT_EQ(bounds_in_px.size(), wayland_window->size_px());
EXPECT_EQ(kBoundsDip, wayland_window->GetBoundsInDIP());
Sync();
}
}
VerifyAndClearExpectations();
}
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandWindowTest,
Values(wl::ServerConfig{
.shell_version = wl::ShellVersion::kStable}));
INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
WaylandWindowTest,
Values(wl::ServerConfig{
.shell_version = wl::ShellVersion::kV6}));
INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test,
WaylandSubsurfaceTest,
Values(wl::ServerConfig{
.shell_version = wl::ShellVersion::kV6}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandSubsurfaceTest,
Values(wl::ServerConfig{
.shell_version = wl::ShellVersion::kStable}));
} // namespace ui