| // 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 <memory> |
| #include <utility> |
| |
| #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/files/file_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.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/cursor/ozone/bitmap_cursor_factory_ozone.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/native_widget_types.h" |
| #include "ui/gfx/overlay_transform.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_subsurface.h" |
| #include "ui/ozone/platform/wayland/host/wayland_zcr_cursor_shapes.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/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/test/mock_platform_window_delegate.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::Eq; |
| using ::testing::InvokeWithoutArgs; |
| using ::testing::Mock; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::StrEq; |
| |
| namespace ui { |
| |
| namespace { |
| |
| 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)); |
| }; |
| |
| } // 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) {} |
| |
| 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 = GetPopupByWindow(menu_window); |
| ASSERT_TRUE(popup); |
| if (GetParam() == kXdgShellV6) { |
| 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, |
| MockPlatformWindowDelegate* 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 = WaylandWindow::Create(delegate, connection_.get(), |
| std::move(properties)); |
| 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 = GetPopupByWindow(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( |
| const std::vector<WaylandWindow*>& dispatching_windows, |
| const std::vector<WaylandWindow*>& non_dispatching_windows) { |
| for (auto* window : dispatching_windows) |
| EXPECT_TRUE(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) { |
| PointerDetails pointer_details(EventPointerType::kTouch, 1); |
| TouchEvent test_touch_event(ET_TOUCH_PRESSED, {1, 1}, base::TimeTicks(), |
| pointer_details); |
| for (auto* window : dispatching_windows) |
| EXPECT_TRUE(window->CanDispatchEvent(&test_touch_event)); |
| for (auto* window : non_dispatching_windows) |
| EXPECT_FALSE(window->CanDispatchEvent(&test_touch_event)); |
| } |
| |
| void VerifyCanDispatchKeyEvents( |
| const std::vector<WaylandWindow*>& dispatching_windows, |
| const std::vector<WaylandWindow*>& non_dispatching_windows) { |
| KeyEvent test_key_event(ET_KEY_PRESSED, VKEY_0, 0); |
| for (auto* window : dispatching_windows) |
| EXPECT_TRUE(window->CanDispatchEvent(&test_key_event)); |
| for (auto* window : non_dispatching_windows) |
| EXPECT_FALSE(window->CanDispatchEvent(&test_key_event)); |
| } |
| |
| wl::TestXdgPopup* GetPopupByWindow(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; |
| } |
| |
| wl::MockXdgSurface* xdg_surface_; |
| |
| MouseEvent test_mouse_event_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(WaylandWindowTest); |
| }; |
| |
| TEST_P(WaylandWindowTest, SetTitle) { |
| EXPECT_CALL(*GetXdgToplevel(), SetTitle(StrEq("hello"))); |
| window_->SetTitle(base::ASCIIToUTF16("hello")); |
| } |
| |
| TEST_P(WaylandWindowTest, MaximizeAndRestore) { |
| const auto kNormalBounds = gfx::Rect{0, 0, 500, 300}; |
| const auto kMaximizedBounds = gfx::Rect{0, 0, 800, 600}; |
| |
| uint32_t serial = 0; |
| |
| // Make sure the window has normal state initially. |
| EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds)); |
| window_->SetBounds(kNormalBounds); |
| 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(0, 0, kMaximizedBounds.width(), |
| kMaximizedBounds.height())); |
| EXPECT_CALL(delegate_, OnActivationChanged(Eq(true))); |
| EXPECT_CALL(delegate_, OnBoundsChanged(kMaximizedBounds)); |
| EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(1); |
| window_->Maximize(); |
| SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), |
| kMaximizedBounds.height(), ++serial, |
| active_maximized.get()); |
| Sync(); |
| VerifyAndClearExpectations(); |
| |
| auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED}); |
| EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, kMaximizedBounds.width(), |
| kMaximizedBounds.height())); |
| EXPECT_CALL(delegate_, OnActivationChanged(Eq(false))); |
| EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0); |
| SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), |
| kMaximizedBounds.height(), ++serial, |
| inactive_maximized.get()); |
| Sync(); |
| VerifyAndClearExpectations(); |
| |
| EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, kMaximizedBounds.width(), |
| kMaximizedBounds.height())); |
| EXPECT_CALL(delegate_, OnActivationChanged(Eq(true))); |
| EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0); |
| SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), |
| kMaximizedBounds.height(), ++serial, |
| active_maximized.get()); |
| Sync(); |
| VerifyAndClearExpectations(); |
| |
| EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, kNormalBounds.width(), |
| kNormalBounds.height())); |
| EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(1); |
| EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0); |
| EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds)); |
| 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) { |
| MockPlatformWindowDelegate delegate; |
| PlatformWindowInitProperties properties; |
| properties.bounds = gfx::Rect(0, 0, 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 = WaylandWindow::Create(&delegate, connection_.get(), |
| std::move(properties)); |
| |
| 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) { |
| MockPlatformWindowDelegate delegate; |
| PlatformWindowInitProperties properties; |
| properties.bounds = gfx::Rect(0, 0, 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 = WaylandWindow::Create(&delegate, connection_.get(), |
| std::move(properties)); |
| |
| 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_->Maximize(); |
| // The state of the window must already be fullscreen one. |
| EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized); |
| |
| Sync(); |
| |
| // Once the surface will be activated, the window state gets updated. |
| EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(1); |
| 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) { |
| EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal); |
| auto normal_bounds = window_->GetBounds(); |
| |
| 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(0, 0, 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(0, 0, normal_bounds.width(), |
| normal_bounds.height())); |
| |
| // 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(0, 0, 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(0, 0, normal_bounds.width(), |
| normal_bounds.height())); |
| |
| 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(0, 0, 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(0, 0, 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(0, 0, normal_bounds.width(), |
| normal_bounds.height())); |
| |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) { |
| const auto kNormalBounds = gfx::Rect{0, 0, 500, 300}; |
| const auto kMaximizedBounds = gfx::Rect{0, 0, 800, 600}; |
| |
| uint32_t serial = 0; |
| |
| // Make sure the window has normal state initially. |
| EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds)); |
| window_->SetBounds(kNormalBounds); |
| 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(0, 0, kMaximizedBounds.width(), |
| kMaximizedBounds.height())); |
| EXPECT_CALL(delegate_, OnActivationChanged(Eq(true))); |
| EXPECT_CALL(delegate_, OnBoundsChanged(kMaximizedBounds)); |
| EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(1); |
| window_->Maximize(); |
| // State changes are synchronous. |
| EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState()); |
| SendConfigureEvent(xdg_surface_, kMaximizedBounds.width(), |
| kMaximizedBounds.height(), ++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(0, 0, kMaximizedBounds.width(), |
| kMaximizedBounds.height())); |
| 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.width(), |
| kMaximizedBounds.height(), ++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(0, 0, kNormalBounds.width(), |
| kNormalBounds.height())); |
| EXPECT_CALL(*GetXdgToplevel(), UnsetFullscreen()); |
| EXPECT_CALL(delegate_, OnBoundsChanged(kNormalBounds)); |
| 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_->GetBounds(); |
| |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| |
| gfx::Rect restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_TRUE(restored_bounds.IsEmpty()); |
| gfx::Rect bounds = window_->GetBounds(); |
| |
| const gfx::Rect maximized_bounds = gfx::Rect(0, 0, 1024, 768); |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(maximized_bounds))); |
| window_->Maximize(); |
| AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(xdg_surface_, maximized_bounds.width(), |
| maximized_bounds.height(), 1, states.get()); |
| Sync(); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(bounds, restored_bounds); |
| |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(current_bounds))); |
| // 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(0, 0, current_bounds.width(), |
| current_bounds.height())); |
| window_->Restore(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); |
| Sync(); |
| bounds = window_->GetBounds(); |
| EXPECT_EQ(bounds, restored_bounds); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, gfx::Rect()); |
| } |
| |
| TEST_P(WaylandWindowTest, RestoreBoundsAfterFullscreen) { |
| const gfx::Rect current_bounds = window_->GetBounds(); |
| |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(xdg_surface_, 0, 0, 1, states.get()); |
| Sync(); |
| |
| gfx::Rect restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, gfx::Rect()); |
| gfx::Rect bounds = window_->GetBounds(); |
| |
| const gfx::Rect fullscreen_bounds = gfx::Rect(0, 0, 1280, 720); |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(fullscreen_bounds))); |
| window_->ToggleFullscreen(); |
| AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); |
| SendConfigureEvent(xdg_surface_, fullscreen_bounds.width(), |
| fullscreen_bounds.height(), 2, states.get()); |
| Sync(); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(bounds, restored_bounds); |
| |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(current_bounds))); |
| // 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(0, 0, current_bounds.width(), |
| current_bounds.height())); |
| window_->Restore(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(xdg_surface_, 0, 0, 3, states.get()); |
| Sync(); |
| bounds = window_->GetBounds(); |
| EXPECT_EQ(bounds, restored_bounds); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, gfx::Rect()); |
| } |
| |
| TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) { |
| const gfx::Rect current_bounds = window_->GetBounds(); |
| |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| |
| gfx::Rect restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, gfx::Rect()); |
| gfx::Rect bounds = window_->GetBounds(); |
| |
| const gfx::Rect maximized_bounds = gfx::Rect(0, 0, 1024, 768); |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(maximized_bounds))); |
| window_->Maximize(); |
| AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(xdg_surface_, maximized_bounds.width(), |
| maximized_bounds.height(), 1, states.get()); |
| Sync(); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(bounds, restored_bounds); |
| |
| const gfx::Rect fullscreen_bounds = gfx::Rect(0, 0, 1280, 720); |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(fullscreen_bounds))); |
| window_->ToggleFullscreen(); |
| AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN, states.get()); |
| SendConfigureEvent(xdg_surface_, fullscreen_bounds.width(), |
| fullscreen_bounds.height(), 2, states.get()); |
| Sync(); |
| gfx::Rect fullscreen_restore_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, fullscreen_restore_bounds); |
| |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(maximized_bounds))); |
| window_->Maximize(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(xdg_surface_, maximized_bounds.width(), |
| maximized_bounds.height(), 3, states.get()); |
| Sync(); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, fullscreen_restore_bounds); |
| |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(current_bounds))); |
| // 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(0, 0, current_bounds.width(), |
| current_bounds.height())); |
| window_->Restore(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(xdg_surface_, 0, 0, 4, states.get()); |
| Sync(); |
| bounds = window_->GetBounds(); |
| EXPECT_EQ(bounds, restored_bounds); |
| restored_bounds = window_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, gfx::Rect()); |
| } |
| |
| TEST_P(WaylandWindowTest, SendsBoundsOnRequest) { |
| const gfx::Rect initial_bounds = window_->GetBounds(); |
| |
| const gfx::Rect new_bounds = gfx::Rect(0, 0, initial_bounds.width() + 10, |
| initial_bounds.height() + 10); |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(new_bounds))); |
| window_->SetBounds(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(0, 0, new_bounds.width(), new_bounds.height())) |
| .Times(2); |
| SendConfigureEvent(xdg_surface_, 0, 0, 2, states.get()); |
| Sync(); |
| |
| // Restored bounds should keep empty value. |
| gfx::Rect restored_bounds = window_->GetRestoredBoundsInPixels(); |
| 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_->GetRestoredBoundsInPixels(); |
| EXPECT_EQ(restored_bounds, gfx::Rect()); |
| } |
| |
| TEST_P(WaylandWindowTest, CanDispatchMouseEventDefault) { |
| EXPECT_FALSE(window_->CanDispatchEvent(&test_mouse_event_)); |
| } |
| |
| TEST_P(WaylandWindowTest, CanDispatchMouseEventFocus) { |
| // SetPointerFocus(true) requires a WaylandPointer. |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| window_->SetPointerFocus(true); |
| EXPECT_TRUE(window_->CanDispatchEvent(&test_mouse_event_)); |
| } |
| |
| TEST_P(WaylandWindowTest, CanDispatchMouseEventUnfocus) { |
| EXPECT_FALSE(window_->has_pointer_focus()); |
| EXPECT_FALSE(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<BitmapCursorOzone>(mojom::CursorType::kPointer); |
| window_->SetCursor(pointer_cursor.get()); |
| |
| EXPECT_CALL(*mock_cursor_shapes, |
| SetCursorShape(ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_HAND)); |
| auto hand_cursor = |
| base::MakeRefCounted<BitmapCursorOzone>(mojom::CursorType::kHand); |
| window_->SetCursor(hand_cursor.get()); |
| |
| EXPECT_CALL(*mock_cursor_shapes, |
| SetCursorShape(ZCR_CURSOR_SHAPES_V1_CURSOR_SHAPE_TYPE_IBEAM)); |
| auto ibeam_cursor = |
| base::MakeRefCounted<BitmapCursorOzone>(mojom::CursorType::kIBeam); |
| window_->SetCursor(ibeam_cursor.get()); |
| } |
| |
| TEST_P(WaylandWindowTest, SetCursorCallsZcrCursorShapesOncePerCursor) { |
| MockZcrCursorShapes* mock_cursor_shapes = InstallMockZcrCursorShapes(); |
| auto hand_cursor = |
| base::MakeRefCounted<BitmapCursorOzone>(mojom::CursorType::kHand); |
| // 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); |
| // The "none" cursor is represented by nullptr. |
| window_->SetCursor(nullptr); |
| } |
| |
| 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<BitmapCursorOzone>( |
| mojom::CursorType::kCustom, SkBitmap(), gfx::Point()); |
| window_->SetCursor(custom_cursor.get()); |
| } |
| |
| ACTION_P(CloneEvent, ptr) { |
| *ptr = Event::Clone(*arg0); |
| } |
| |
| 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. |
| |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(gfx::Rect(0, 0, 1000, 1000)))); |
| // 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(0, 0, 1000, 1000)).Times(1); |
| EXPECT_CALL(*xdg_surface_, AckConfigure(12)); |
| SendConfigureEvent(xdg_surface_, 1000, 1000, 12, states.get()); |
| |
| Sync(); |
| |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(gfx::Rect(0, 0, 1500, 1000)))); |
| EXPECT_CALL(*xdg_surface_, SetWindowGeometry(0, 0, 1500, 1000)).Times(1); |
| EXPECT_CALL(*xdg_surface_, AckConfigure(13)); |
| SendConfigureEvent(xdg_surface_, 1500, 1000, 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(0, 0, 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) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| |
| // SetPointerFocus(true) requires a WaylandPointer. |
| wl_seat_send_capabilities( |
| server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer() && connection_->touch()); |
| window_->SetPointerFocus(true); |
| |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(0, 0, 10, 10), &menu_window_delegate); |
| EXPECT_TRUE(menu_window); |
| |
| Sync(); |
| |
| window_->SetPointerFocus(false); |
| 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(0, 0, 10, 10), &menu_window_delegate); |
| EXPECT_TRUE(menu_window); |
| |
| Sync(); |
| |
| window_->set_touch_focus(true); |
| |
| menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(0, 0, 10, 10), &menu_window_delegate); |
| EXPECT_TRUE(menu_window); |
| |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, CreateAndDestroyNestedMenuWindow) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| gfx::AcceleratedWidget menu_window_widget; |
| EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_)) |
| .WillOnce(SaveArg<0>(&menu_window_widget)); |
| |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, widget_, gfx::Rect(0, 0, 10, 10), |
| &menu_window_delegate); |
| EXPECT_TRUE(menu_window); |
| ASSERT_NE(menu_window_widget, gfx::kNullAcceleratedWidget); |
| |
| Sync(); |
| |
| MockPlatformWindowDelegate 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) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| 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_->pointer()); |
| window_->SetPointerFocus(true); |
| |
| // 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)); |
| |
| 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)); |
| |
| 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)); |
| |
| 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. |
| MockPlatformWindowDelegate nested_menu_window_delegate; |
| 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(); |
| |
| window_->SetPointerFocus(false); |
| nested_menu_window->SetPointerFocus(true); |
| |
| // 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)); |
| |
| Sync(); |
| |
| ASSERT_TRUE(event4->IsLocatedEvent()); |
| EXPECT_EQ(event4->AsLocatedEvent()->location(), gfx::Point(7, 16)); |
| |
| menu_window.reset(); |
| } |
| |
| // 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) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| 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_|. |
| MockPlatformWindowDelegate toplevel_window2_delegate; |
| std::unique_ptr<WaylandWindow> toplevel_window2 = |
| CreateWaylandWindowWithParams( |
| PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget, |
| window_->GetBounds(), &toplevel_window2_delegate); |
| EXPECT_TRUE(toplevel_window2); |
| |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| window_->SetPointerFocus(true); |
| |
| // 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)); |
| |
| 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. |
| window_->SetPointerFocus(false); |
| toplevel_window2->SetPointerFocus(true); |
| |
| 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)); |
| |
| Sync(); |
| |
| ASSERT_TRUE(event->IsLocatedEvent()); |
| EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 20)); |
| } |
| |
| TEST_P(WaylandWindowTest, DispatchesKeyboardEventToToplevelWindow) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| 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_->keyboard()); |
| menu_window->set_keyboard_focus(true); |
| |
| // 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) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| gfx::AcceleratedWidget menu_window_widget; |
| EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_)) |
| .WillOnce(SaveArg<0>(&menu_window_widget)); |
| |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, widget_, gfx::Rect(0, 0, 10, 10), |
| &menu_window_delegate); |
| EXPECT_TRUE(menu_window); |
| |
| Sync(); |
| |
| MockPlatformWindowDelegate 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(); |
| |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER | |
| WL_SEAT_CAPABILITY_KEYBOARD | |
| WL_SEAT_CAPABILITY_TOUCH); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| ASSERT_TRUE(connection_->touch()); |
| ASSERT_TRUE(connection_->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)); |
| |
| 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_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) { |
| 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); |
| |
| 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, ToplevelWindowUpdateBufferScale) { |
| VerifyAndClearExpectations(); |
| |
| // Buffer scale must be 1 when no output has been entered by the window. |
| EXPECT_EQ(1, window_->buffer_scale()); |
| |
| // Creating an output with scale 1. |
| wl::TestOutput* output1 = server_.CreateAndInitializeOutput(); |
| output1->SetRect(gfx::Rect(0, 0, 1920, 1080)); |
| output1->SetScale(1); |
| Sync(); |
| |
| // Creating an output with scale 2. |
| wl::TestOutput* output2 = server_.CreateAndInitializeOutput(); |
| output2->SetRect(gfx::Rect(0, 0, 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_->buffer_scale()); |
| EXPECT_EQ(gfx::Rect(0, 0, 800, 600), window_->GetBounds()); |
| |
| // 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_->buffer_scale()); |
| EXPECT_EQ(gfx::Rect(0, 0, 1600, 1200), window_->GetBounds()); |
| } |
| |
| TEST_P(WaylandWindowTest, AuxiliaryWindowUpdateBufferScale) { |
| VerifyAndClearExpectations(); |
| |
| // Creating an output with scale 1. |
| wl::TestOutput* output1 = server_.CreateAndInitializeOutput(); |
| output1->SetRect(gfx::Rect(0, 0, 1920, 1080)); |
| output1->SetScale(1); |
| Sync(); |
| |
| // Creating an output with scale 2. |
| wl::TestOutput* output2 = server_.CreateAndInitializeOutput(); |
| output2->SetRect(gfx::Rect(0, 0, 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(); |
| |
| // Creating a tooltip on |window_|. |
| window_->SetPointerFocus(true); |
| gfx::Rect subsurface_bounds(15, 15, 10, 10); |
| std::unique_ptr<WaylandWindow> auxiliary_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kTooltip, |
| gfx::kNullAcceleratedWidget, |
| subsurface_bounds, &delegate_); |
| EXPECT_TRUE(auxiliary_window); |
| |
| auxiliary_window->Show(false); |
| |
| // |auxiliary_window| should inherit its buffer scale from the focused window. |
| EXPECT_EQ(1, auxiliary_window->buffer_scale()); |
| EXPECT_EQ(subsurface_bounds, auxiliary_window->GetBounds()); |
| auxiliary_window->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_->buffer_scale()); |
| auxiliary_window->Show(false); |
| |
| // |auxiliary_window|'s scale and bounds must change whenever its parents |
| // scale is changed. |
| EXPECT_EQ(2, window_->buffer_scale()); |
| EXPECT_EQ(2, auxiliary_window->buffer_scale()); |
| EXPECT_EQ(gfx::ScaleToRoundedRect(subsurface_bounds, 2), |
| auxiliary_window->GetBounds()); |
| |
| auxiliary_window->Hide(); |
| window_->SetPointerFocus(false); |
| } |
| |
| // Tests WaylandWindow repositions menu windows to be relative to parent window |
| // in a right way. |
| TEST_P(WaylandWindowTest, AdjustPopupBounds) { |
| PopupPosition menu_window_positioner, nested_menu_window_positioner; |
| |
| if (GetParam() == kXdgShellV6) { |
| menu_window_positioner = { |
| gfx::Rect(439, 46, 1, 30), gfx::Size(287, 409), |
| ZXDG_POSITIONER_V6_ANCHOR_BOTTOM | ZXDG_POSITIONER_V6_ANCHOR_RIGHT, |
| ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT, |
| ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_X | |
| ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_FLIP_Y | |
| ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y}; |
| |
| nested_menu_window_positioner = { |
| gfx::Rect(4, 80, 279, 1), gfx::Size(305, 99), |
| 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_X | |
| ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_SLIDE_Y | |
| ZXDG_POSITIONER_V6_CONSTRAINT_ADJUSTMENT_RESIZE_Y}; |
| } else { |
| menu_window_positioner = { |
| gfx::Rect(439, 46, 1, 30), gfx::Size(287, 409), |
| XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT, |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y}; |
| nested_menu_window_positioner = { |
| gfx::Rect(4, 80, 279, 1), gfx::Size(305, 99), |
| XDG_POSITIONER_ANCHOR_TOP_RIGHT, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT, |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y | |
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y}; |
| } |
| |
| auto* toplevel_window = window_.get(); |
| toplevel_window->SetBounds(gfx::Rect(0, 0, 739, 574)); |
| |
| // Case 1: the top menu window is positioned normally. |
| MockPlatformWindowDelegate menu_window_delegate; |
| gfx::Rect menu_window_bounds(gfx::Point(440, 76), |
| 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->GetBounds(), menu_window_bounds); |
| |
| // Case 2: the nested menu window is positioned normally. |
| MockPlatformWindowDelegate nested_menu_window_delegate; |
| gfx::Rect nested_menu_window_bounds(gfx::Point(723, 156), |
| 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); |
| |
| EXPECT_CALL(nested_menu_window_delegate, OnBoundsChanged(_)).Times(0); |
| const gfx::Point origin(nested_menu_window_positioner.anchor_rect.x() + |
| nested_menu_window_positioner.anchor_rect.width(), |
| nested_menu_window_positioner.anchor_rect.y()); |
| gfx::Rect calculated_nested_bounds = nested_menu_window_bounds; |
| calculated_nested_bounds.set_origin(origin); |
| SendConfigureEventPopup(nested_menu_window.get(), calculated_nested_bounds); |
| |
| Sync(); |
| |
| EXPECT_EQ(nested_menu_window->GetBounds(), nested_menu_window_bounds); |
| |
| // Case 3: imagine the menu window was positioned near to the right edge of a |
| // display. Nothing changes in the way how WaylandWindow calculates bounds, |
| // because the Wayland compositor does not provide global location of windows. |
| // Though, the compositor can reposition the window (flip along x or y axis or |
| // slide along those axis). WaylandWindow just needs to correctly translate |
| // bounds from relative to parent to be relative to screen. The Wayland |
| // compositor does not reposition the menu, because it fits the screen, but |
| // the nested menu window is repositioned to the left. |
| EXPECT_CALL( |
| nested_menu_window_delegate, |
| OnBoundsChanged(gfx::Rect({139, 156}, nested_menu_window_bounds.size()))); |
| calculated_nested_bounds.set_origin({-301, 80}); |
| SendConfigureEventPopup(nested_menu_window.get(), calculated_nested_bounds); |
| |
| Sync(); |
| |
| // Case 4: imagine the top level window was moved down to the bottom edge of a |
| // display and only tab strip with 3-dot menu buttons left visible. In this |
| // case, Chromium also does not know about that and positions the window |
| // normally (normal bounds are sent), but the Wayland compositor flips the top |
| // menu window along y-axis and fixes bounds of a top level window so that it |
| // is located (from the Chromium point of view) below origin of the menu |
| // window. |
| EXPECT_CALL(delegate_, OnBoundsChanged( |
| gfx::Rect({0, 363}, window_->GetBounds().size()))); |
| EXPECT_CALL(menu_window_delegate, |
| OnBoundsChanged(gfx::Rect({440, 0}, menu_window_bounds.size()))); |
| SendConfigureEventPopup(menu_window.get(), |
| gfx::Rect({440, -363}, menu_window_bounds.size())); |
| |
| Sync(); |
| |
| // The nested menu window is also repositioned accordingly, but it's not |
| // Wayland compositor reposition, but rather reposition from the Chromium |
| // side. Thus, we have to check that anchor rect is correct. |
| nested_menu_window.reset(); |
| nested_menu_window_bounds.set_origin({723, 258}); |
| nested_menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, menu_window->GetWidget(), |
| nested_menu_window_bounds, &nested_menu_window_delegate); |
| EXPECT_TRUE(nested_menu_window); |
| |
| Sync(); |
| |
| // We must get the anchor on gfx::Point(4, 258). |
| nested_menu_window_positioner.anchor_rect.set_origin({4, 258}); |
| VerifyXdgPopupPosition(nested_menu_window.get(), |
| nested_menu_window_positioner); |
| |
| Sync(); |
| |
| EXPECT_CALL(nested_menu_window_delegate, OnBoundsChanged(_)).Times(0); |
| calculated_nested_bounds.set_origin({283, 258}); |
| SendConfigureEventPopup(nested_menu_window.get(), calculated_nested_bounds); |
| |
| Sync(); |
| |
| // Case 5: this case involves case 4. Thus, it concerns only the nested menu |
| // window. imagine that the top menu window is flipped along y-axis and |
| // positioned near to the right side of a display. The nested menu window is |
| // flipped along x-axis by the compositor and WaylandWindow must calculate |
| // bounds back to be relative to display correctly. If the window is near to |
| // the left edge of a display, nothing is going to change, and the origin will |
| // be the same as in the previous case. |
| EXPECT_CALL( |
| nested_menu_window_delegate, |
| OnBoundsChanged(gfx::Rect({149, 258}, nested_menu_window_bounds.size()))); |
| calculated_nested_bounds.set_origin({-291, 258}); |
| SendConfigureEventPopup(nested_menu_window.get(), calculated_nested_bounds); |
| |
| Sync(); |
| |
| // Case 6: imagine the top level window was moved back to normal position. In |
| // this case, the Wayland compositor positions the menu window normally and |
| // the WaylandWindow repositions the top level window back to 0,0 (which had |
| // an offset to compensate the position of the menu window fliped along |
| // y-axis. It just has had negative y value, which is wrong for Chromium. |
| EXPECT_CALL(delegate_, |
| OnBoundsChanged(gfx::Rect({0, 0}, window_->GetBounds().size()))); |
| EXPECT_CALL(menu_window_delegate, |
| OnBoundsChanged(gfx::Rect({440, 76}, menu_window_bounds.size()))); |
| SendConfigureEventPopup(menu_window.get(), |
| gfx::Rect({440, 76}, menu_window_bounds.size())); |
| |
| Sync(); |
| |
| VerifyAndClearExpectations(); |
| } |
| |
| 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(0, 0, 500, 600); |
| auto state_array = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED}); |
| SendConfigureEvent(xdg_surface_, new_bounds.width(), new_bounds.height(), 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.width(), new_bounds.height(), 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() == kXdgShellV6) |
| zxdg_toplevel_v6_send_close(xdg_surface_->xdg_toplevel()->resource()); |
| else |
| xdg_toplevel_send_close(xdg_surface_->xdg_toplevel()->resource()); |
| |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, AuxiliaryWindowSimpleParent) { |
| VerifyAndClearExpectations(); |
| |
| // Auxiliary window must ignore the parent provided by aura and should always |
| // use focused window instead. |
| gfx::Rect subsurface_bounds(gfx::Point(15, 15), gfx::Size(10, 10)); |
| std::unique_ptr<WaylandWindow> auxiliary_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kTooltip, |
| gfx::kNullAcceleratedWidget, |
| subsurface_bounds, &delegate_); |
| EXPECT_TRUE(auxiliary_window); |
| |
| window_->SetPointerFocus(true); |
| auxiliary_window->Show(false); |
| |
| Sync(); |
| |
| auto* mock_surface_subsurface = server_.GetObject<wl::MockSurface>( |
| auxiliary_window->root_surface()->GetSurfaceId()); |
| auto* test_subsurface = mock_surface_subsurface->sub_surface(); |
| |
| EXPECT_EQ(test_subsurface->position(), subsurface_bounds.origin()); |
| EXPECT_FALSE(test_subsurface->sync()); |
| EXPECT_EQ(mock_surface_subsurface->opaque_region(), |
| gfx::Rect(subsurface_bounds.size())); |
| |
| auto* parent_resource = |
| server_ |
| .GetObject<wl::MockSurface>(window_->root_surface()->GetSurfaceId()) |
| ->resource(); |
| EXPECT_EQ(parent_resource, test_subsurface->parent_resource()); |
| |
| auxiliary_window->Hide(); |
| window_->SetPointerFocus(false); |
| } |
| |
| // Case 1: When the menu bounds are positive and there is a positive, |
| // non-zero anchor width |
| TEST_P(WaylandWindowTest, NestedPopupMenu) { |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect menu_window_bounds(gfx::Rect(4, 20, 8, 20)); |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds, |
| &delegate_); |
| EXPECT_TRUE(menu_window); |
| |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect nestedPopup_bounds(gfx::Rect(10, 30, 40, 20)); |
| std::unique_ptr<WaylandWindow> nested_popup_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kPopup, |
| menu_window->GetWidget(), |
| nestedPopup_bounds, &delegate_); |
| EXPECT_TRUE(nested_popup_window); |
| |
| VerifyAndClearExpectations(); |
| |
| nested_popup_window->SetPointerFocus(true); |
| |
| Sync(); |
| |
| auto* mock_surface_nested_popup = GetPopupByWindow(nested_popup_window.get()); |
| |
| ASSERT_TRUE(mock_surface_nested_popup); |
| |
| auto anchor_width = (mock_surface_nested_popup->anchor_rect()).width(); |
| EXPECT_EQ(4, anchor_width); |
| |
| nested_popup_window->SetPointerFocus(false); |
| } |
| |
| // Case 2: When the menu bounds are positive and there is a negative or |
| // zero anchor width |
| TEST_P(WaylandWindowTest, NestedPopupMenu1) { |
| VerifyAndClearExpectations(); |
| |
| 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); |
| |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect nestedPopup_bounds(gfx::Rect(10, 30, 10, 20)); |
| std::unique_ptr<WaylandWindow> nested_popup_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kPopup, |
| menu_window->GetWidget(), |
| nestedPopup_bounds, &delegate_); |
| EXPECT_TRUE(nested_popup_window); |
| |
| VerifyAndClearExpectations(); |
| |
| nested_popup_window->SetPointerFocus(true); |
| |
| Sync(); |
| |
| auto* mock_surface_nested_popup = GetPopupByWindow(nested_popup_window.get()); |
| |
| ASSERT_TRUE(mock_surface_nested_popup); |
| |
| auto anchor_width = (mock_surface_nested_popup->anchor_rect()).width(); |
| EXPECT_EQ(1, anchor_width); |
| |
| nested_popup_window->SetPointerFocus(false); |
| } |
| |
| // Case 3: When the menu bounds are negative and there is a positive, |
| // non-zero anchor width |
| TEST_P(WaylandWindowTest, NestedPopupMenu2) { |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect menu_window_bounds(gfx::Rect(10, 20, 40, 20)); |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds, |
| &delegate_); |
| EXPECT_TRUE(menu_window); |
| |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect nestedPopup_bounds(gfx::Rect(5, 30, 21, 20)); |
| std::unique_ptr<WaylandWindow> nested_popup_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kPopup, |
| menu_window->GetWidget(), |
| nestedPopup_bounds, &delegate_); |
| EXPECT_TRUE(nested_popup_window); |
| |
| VerifyAndClearExpectations(); |
| |
| nested_popup_window->SetPointerFocus(true); |
| |
| Sync(); |
| |
| auto* mock_surface_nested_popup = GetPopupByWindow(nested_popup_window.get()); |
| |
| ASSERT_TRUE(mock_surface_nested_popup); |
| |
| auto anchor_width = (mock_surface_nested_popup->anchor_rect()).width(); |
| EXPECT_EQ(8, anchor_width); |
| |
| nested_popup_window->SetPointerFocus(false); |
| } |
| |
| // Case 4: When the menu bounds are negative and there is a negative, |
| // zero anchor width |
| TEST_P(WaylandWindowTest, NestedPopupMenu3) { |
| 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); |
| |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect nestedPopup_bounds(gfx::Rect(5, 30, 21, 20)); |
| std::unique_ptr<WaylandWindow> nested_popup_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kPopup, |
| menu_window->GetWidget(), |
| nestedPopup_bounds, &delegate_); |
| EXPECT_TRUE(nested_popup_window); |
| |
| VerifyAndClearExpectations(); |
| |
| nested_popup_window->SetPointerFocus(true); |
| |
| Sync(); |
| |
| auto* mock_surface_nested_popup = GetPopupByWindow(nested_popup_window.get()); |
| |
| ASSERT_TRUE(mock_surface_nested_popup); |
| |
| auto anchor_width = (mock_surface_nested_popup->anchor_rect()).width(); |
| |
| EXPECT_EQ(1, anchor_width); |
| |
| nested_popup_window->SetPointerFocus(false); |
| } |
| |
| TEST_P(WaylandWindowTest, AuxiliaryWindowNestedParent) { |
| VerifyAndClearExpectations(); |
| |
| gfx::Rect menu_window_bounds(gfx::Point(10, 10), gfx::Size(100, 100)); |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), menu_window_bounds, |
| &delegate_); |
| EXPECT_TRUE(menu_window); |
| |
| VerifyAndClearExpectations(); |
| menu_window->SetPointerFocus(true); |
| |
| gfx::Rect subsurface_bounds(gfx::Point(15, 15), gfx::Size(10, 10)); |
| std::unique_ptr<WaylandWindow> auxiliary_window = |
| CreateWaylandWindowWithParams(PlatformWindowType::kTooltip, |
| menu_window->GetWidget(), subsurface_bounds, |
| &delegate_); |
| EXPECT_TRUE(auxiliary_window); |
| |
| VerifyAndClearExpectations(); |
| |
| auxiliary_window->Show(false); |
| |
| Sync(); |
| |
| auto* mock_surface_subsurface = server_.GetObject<wl::MockSurface>( |
| auxiliary_window->root_surface()->GetSurfaceId()); |
| auto* test_subsurface = mock_surface_subsurface->sub_surface(); |
| |
| auto new_origin = subsurface_bounds.origin() - |
| menu_window_bounds.origin().OffsetFromOrigin(); |
| EXPECT_EQ(test_subsurface->position(), new_origin); |
| EXPECT_EQ(mock_surface_subsurface->opaque_region(), |
| gfx::Rect(subsurface_bounds.size())); |
| |
| menu_window->SetPointerFocus(false); |
| } |
| |
| TEST_P(WaylandWindowTest, OnSizeConstraintsChanged) { |
| const bool kBooleans[] = {false, true}; |
| for (bool has_min_size : kBooleans) { |
| for (bool has_max_size : kBooleans) { |
| base::Optional<gfx::Size> min_size = |
| has_min_size ? base::Optional<gfx::Size>(gfx::Size(100, 200)) |
| : base::nullopt; |
| base::Optional<gfx::Size> max_size = |
| has_max_size ? base::Optional<gfx::Size>(gfx::Size(300, 400)) |
| : base::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) { |
| MockPlatformWindowDelegate delegate; |
| auto window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget, |
| gfx::Rect(0, 0, 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) { |
| MockPlatformWindowDelegate delegate; |
| auto window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), gfx::Rect(0, 0, 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, RemovesReattachesBackgroundOnHideShow) { |
| EXPECT_TRUE(connection_->buffer_manager_host()); |
| |
| auto interface_ptr = connection_->buffer_manager_host()->BindInterface(); |
| buffer_manager_gpu_->Initialize(std::move(interface_ptr), {}, false, false); |
| |
| // 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. |
| MockPlatformWindowDelegate delegate; |
| auto window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget, |
| gfx::Rect(0, 0, 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<ui::ozone::mojom::WaylandOverlayConfigPtr> overlays; |
| ui::ozone::mojom::WaylandOverlayConfigPtr background{ |
| ui::ozone::mojom::WaylandOverlayConfig::New()}; |
| background->z_order = INT32_MIN; |
| background->transform = gfx::OVERLAY_TRANSFORM_NONE; |
| background->buffer_id = buffer_id1; |
| overlays.push_back(std::move(background)); |
| buffer_manager_gpu_->CommitOverlays(window->GetWidget(), std::move(overlays)); |
| mock_surface->SendFrameCallback(); |
| |
| Sync(); |
| |
| EXPECT_NE(mock_surface->attached_buffer(), nullptr); |
| |
| // Hiding window attaches a nil wl_buffer as background. |
| window->Hide(); |
| mock_surface->SendFrameCallback(); |
| |
| Sync(); |
| |
| EXPECT_EQ(mock_surface->attached_buffer(), nullptr); |
| |
| mock_surface->ReleaseBuffer(mock_surface->prev_attached_buffer()); |
| window->Show(false); |
| |
| Sync(); |
| |
| SendConfigureEvent(mock_surface->xdg_surface(), 100, 100, 2, states.get()); |
| |
| // Commit a frame with only the primary_plane. |
| overlays.clear(); |
| ui::ozone::mojom::WaylandOverlayConfigPtr primary{ |
| ui::ozone::mojom::WaylandOverlayConfig::New()}; |
| primary->z_order = 0; |
| primary->transform = gfx::OVERLAY_TRANSFORM_NONE; |
| primary->buffer_id = buffer_id2; |
| overlays.push_back(std::move(primary)); |
| buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 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 base::string16 kTitle(base::UTF8ToUTF16("WaylandWindowTest")); |
| |
| PlatformWindowInitProperties properties; |
| properties.bounds = gfx::Rect(0, 0, 100, 100); |
| properties.type = PlatformWindowType::kWindow; |
| properties.wm_class_class = kAppId; |
| |
| MockPlatformWindowDelegate delegate; |
| auto window = WaylandWindow::Create(&delegate, connection_.get(), |
| std::move(properties)); |
| 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(std::string(kAppId), 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. |
| base::Optional<gfx::Size> min_size(gfx::Size(1, 1)); |
| base::Optional<gfx::Size> max_size(gfx::Size(100, 100)); |
| EXPECT_CALL(delegate, GetMinimumSizeForWindow()).WillOnce(Return(min_size)); |
| EXPECT_CALL(delegate, GetMaximumSizeForWindow()).WillOnce(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(std::string(kAppId), 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) { |
| wl_seat_send_capabilities( |
| server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD); |
| |
| Sync(); |
| |
| constexpr uint32_t enter_serial = 1; |
| constexpr uint32_t button_press_serial = 2; |
| constexpr uint32_t button_release_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 - 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. |
| MockPlatformWindowDelegate delegate; |
| auto popup = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), gfx::Rect(0, 0, 50, 50), |
| &delegate); |
| ASSERT_TRUE(popup); |
| |
| Sync(); |
| |
| auto* test_popup = GetPopupByWindow(popup.get()); |
| ASSERT_TRUE(test_popup); |
| EXPECT_NE(test_popup->grab_serial(), button_release_serial); |
| EXPECT_EQ(test_popup->grab_serial(), button_press_serial); |
| } |
| |
| // 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) { |
| 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_up(server_.seat()->touch()->resource(), touch_up_serial, 1000, |
| 0 /* id */); |
| |
| Sync(); |
| |
| // Create a popup window and verify the client used correct serial. |
| MockPlatformWindowDelegate delegate; |
| auto popup = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), gfx::Rect(0, 0, 50, 50), |
| &delegate); |
| ASSERT_TRUE(popup); |
| |
| Sync(); |
| |
| auto* test_popup = GetPopupByWindow(popup.get()); |
| ASSERT_TRUE(test_popup); |
| |
| // Touch events are the exception. We can't use the serial that was sent |
| // before the "up" event. Otherwise, some compositors may dismiss popups. |
| // Thus, no serial must be used. |
| EXPECT_EQ(test_popup->grab_serial(), 0U); |
| |
| 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)); |
| |
| Sync(); |
| |
| popup->Show(false); |
| |
| Sync(); |
| |
| test_popup = GetPopupByWindow(popup.get()); |
| ASSERT_TRUE(test_popup); |
| |
| EXPECT_EQ(test_popup->grab_serial(), touch_down_serial); |
| } |
| |
| // 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, 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, window_->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, window_->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. |
| MockPlatformWindowDelegate delegate; |
| auto popup = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window_->GetWidget(), gfx::Rect(0, 0, 50, 50), |
| &delegate); |
| ASSERT_TRUE(popup); |
| |
| Sync(); |
| |
| auto* test_popup = GetPopupByWindow(popup.get()); |
| ASSERT_TRUE(test_popup); |
| EXPECT_EQ(test_popup->grab_serial(), 0u); |
| } |
| |
| TEST_P(WaylandWindowTest, OneWaylandSubsurface) { |
| VerifyAndClearExpectations(); |
| |
| std::unique_ptr<WaylandWindow> window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget, |
| gfx::Rect(0, 0, 640, 480), &delegate_); |
| EXPECT_TRUE(window); |
| |
| gfx::Rect subsurface_bounds(gfx::Point(15, 15), gfx::Size(10, 10)); |
| bool result = window->RequestSubsurface(); |
| EXPECT_TRUE(result); |
| |
| 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(gfx::OVERLAY_TRANSFORM_NONE, |
| gfx::RectF(), subsurface_bounds, |
| true, nullptr, nullptr); |
| connection_->ScheduleFlush(); |
| |
| 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()); |
| |
| EXPECT_EQ(test_subsurface->position(), subsurface_bounds.origin()); |
| EXPECT_TRUE(test_subsurface->sync()); |
| } |
| |
| TEST_P(WaylandWindowTest, UsesCorrectParentForChildrenWindows) { |
| uint32_t serial = 0; |
| |
| MockPlatformWindowDelegate window_delegate; |
| std::unique_ptr<WaylandWindow> window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget, |
| gfx::Rect(10, 10, 100, 100), &window_delegate); |
| EXPECT_TRUE(window); |
| |
| window->Show(false); |
| |
| std::unique_ptr<WaylandWindow> another_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kWindow, gfx::kNullAcceleratedWidget, |
| gfx::Rect(10, 10, 300, 400), &window_delegate); |
| EXPECT_TRUE(another_window); |
| |
| another_window->Show(false); |
| |
| Sync(); |
| |
| auto* window1 = window.get(); |
| auto* window2 = window_.get(); |
| auto* window3 = another_window.get(); |
| |
| // Make sure windows are not "active". |
| auto empty_state = MakeStateArray({}); |
| SendConfigureEvent(xdg_surface_, 0, 0, ++serial, empty_state.get()); |
| auto* xdg_surface_window = |
| server_ |
| .GetObject<wl::MockSurface>(window->root_surface()->GetSurfaceId()) |
| ->xdg_surface(); |
| SendConfigureEvent(xdg_surface_window, 0, 0, ++serial, empty_state.get()); |
| auto* xdg_surface_another_window = |
| server_ |
| .GetObject<wl::MockSurface>( |
| another_window->root_surface()->GetSurfaceId()) |
| ->xdg_surface(); |
| SendConfigureEvent(xdg_surface_another_window, 0, 0, ++serial, |
| empty_state.get()); |
| |
| Sync(); |
| |
| // Case 1: provided parent window's widget.. |
| MockPlatformWindowDelegate menu_window_delegate; |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, window1->GetWidget(), |
| gfx::Rect(10, 10, 10, 10), &menu_window_delegate); |
| |
| EXPECT_TRUE(menu_window->parent_window() == window1); |
| |
| // Case 2: didn't provide parent window's widget - must use current focused. |
| // |
| // Subcase 1: pointer focus. |
| window2->SetPointerFocus(true); |
| menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(10, 10, 10, 10), &menu_window_delegate); |
| |
| EXPECT_TRUE(menu_window->parent_window() == window2); |
| EXPECT_TRUE(wl::IsMenuType(menu_window->type())); |
| |
| // Subcase 2: keyboard focus. |
| window2->SetPointerFocus(false); |
| window2->set_keyboard_focus(true); |
| menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(10, 10, 10, 10), &menu_window_delegate); |
| |
| // Mustn't be able to create a menu window, but rather creates a toplevel |
| // window as we must provide at least something. |
| EXPECT_TRUE(menu_window); |
| // Make it create xdg objects. |
| menu_window->Show(false); |
| |
| Sync(); |
| |
| auto* menu_window_xdg = |
| server_ |
| .GetObject<wl::MockSurface>( |
| another_window->root_surface()->GetSurfaceId()) |
| ->xdg_surface(); |
| EXPECT_TRUE(menu_window_xdg); |
| EXPECT_TRUE(menu_window_xdg->xdg_toplevel()); |
| EXPECT_FALSE(menu_window_xdg->xdg_popup()); |
| |
| // Subcase 3: touch focus. |
| window2->set_keyboard_focus(false); |
| window2->set_touch_focus(true); |
| menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(10, 10, 10, 10), &menu_window_delegate); |
| |
| EXPECT_TRUE(menu_window->parent_window() == window2); |
| EXPECT_TRUE(wl::IsMenuType(menu_window->type())); |
| |
| // Case 3: neither of the windows are focused. However, there is one that is |
| // active. Must use that then. |
| window2->set_touch_focus(false); |
| |
| auto active = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(xdg_surface_another_window, 0, 0, ++serial, active.get()); |
| Sync(); |
| |
| menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(10, 10, 10, 10), &menu_window_delegate); |
| |
| EXPECT_TRUE(menu_window->parent_window() == window3); |
| EXPECT_TRUE(wl::IsMenuType(menu_window->type())); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, |
| WaylandWindowTest, |
| ::testing::Values(kXdgShellStable)); |
| INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test, |
| WaylandWindowTest, |
| ::testing::Values(kXdgShellV6)); |
| |
| } // namespace ui |