| // 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/wayland_window.h" |
| |
| #include <linux/input.h> |
| #include <wayland-server-core.h> |
| #include <xdg-shell-unstable-v5-server-protocol.h> |
| #include <xdg-shell-unstable-v6-server-protocol.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/events/base_event_utils.h" |
| #include "ui/events/event.h" |
| #include "ui/ozone/platform/wayland/fake_server.h" |
| #include "ui/ozone/platform/wayland/wayland_test.h" |
| #include "ui/ozone/test/mock_platform_window_delegate.h" |
| #include "ui/platform_window/platform_window_init_properties.h" |
| |
| using ::testing::Eq; |
| using ::testing::Mock; |
| using ::testing::SaveArg; |
| using ::testing::StrEq; |
| using ::testing::_; |
| |
| namespace ui { |
| |
| namespace { |
| |
| 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_; |
| }; |
| |
| } // 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 SendConfigureEvent(int width, |
| int height, |
| uint32_t serial, |
| struct wl_array* states) { |
| if (!xdg_surface_->xdg_toplevel()) { |
| xdg_surface_send_configure(xdg_surface_->resource(), width, height, |
| states, serial); |
| return; |
| } |
| |
| // In xdg_shell_v6, both surfaces send serial configure event and toplevel |
| // surfaces send other data like states, heights and widths. |
| zxdg_surface_v6_send_configure(xdg_surface_->resource(), serial); |
| ASSERT_TRUE(xdg_surface_->xdg_toplevel()); |
| zxdg_toplevel_v6_send_configure(xdg_surface_->xdg_toplevel()->resource(), |
| width, height, states); |
| } |
| |
| // Depending on a shell version, xdg_surface_ or xdg_toplevel surface should |
| // get the mock calls. This method decided, which surface to use. |
| wl::MockXdgSurface* GetXdgSurface() { |
| if (GetParam() == kXdgShellV5) |
| return xdg_surface_; |
| 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_SURFACE_STATE_ACTIVATED, states.get()); |
| return states; |
| } |
| |
| 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; |
| |
| std::unique_ptr<WaylandWindow> window = |
| std::make_unique<WaylandWindow>(delegate, connection_.get()); |
| |
| EXPECT_TRUE(window->Initialize(std::move(properties))); |
| return window; |
| } |
| |
| wl::MockXdgSurface* xdg_surface_; |
| |
| MouseEvent test_mouse_event_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(WaylandWindowTest); |
| }; |
| |
| TEST_P(WaylandWindowTest, SetTitle) { |
| EXPECT_CALL(*GetXdgSurface(), SetTitle(StrEq("hello"))); |
| window_->SetTitle(base::ASCIIToUTF16("hello")); |
| } |
| |
| TEST_P(WaylandWindowTest, MaximizeAndRestore) { |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_MAXIMIZED))); |
| AddStateToWlArray(XDG_SURFACE_STATE_MAXIMIZED, states.get()); |
| |
| EXPECT_CALL(*GetXdgSurface(), SetMaximized()); |
| window_->Maximize(); |
| SendConfigureEvent(0, 0, 1, states.get()); |
| Sync(); |
| |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_NORMAL))); |
| EXPECT_CALL(*GetXdgSurface(), UnsetMaximized()); |
| window_->Restore(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(0, 0, 2, states.get()); |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, Minimize) { |
| ScopedWlArray states; |
| |
| // Make sure the window is initialized to normal state from the beginning. |
| EXPECT_EQ(PLATFORM_WINDOW_STATE_NORMAL, window_->GetPlatformWindowState()); |
| SendConfigureEvent(0, 0, 1, states.get()); |
| Sync(); |
| |
| EXPECT_CALL(*GetXdgSurface(), SetMinimized()); |
| // Wayland compositor doesn't notify clients about minimized state, but rather |
| // if a window is not activated. Thus, a WaylandWindow marks itself as being |
| // minimized and as soon as a configuration event with not activated state |
| // comes, its state is changed to minimized. This EXPECT_CALL ensures this |
| // behaviour. |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_MINIMIZED))); |
| window_->Minimize(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = ScopedWlArray(); |
| SendConfigureEvent(0, 0, 2, states.get()); |
| Sync(); |
| |
| // 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 and doesn't notify its delegate. |
| EXPECT_CALL(delegate_, OnWindowStateChanged(_)).Times(0); |
| SendConfigureEvent(0, 0, 3, states.get()); |
| Sync(); |
| |
| // And one last time to ensure the behaviour. |
| SendConfigureEvent(0, 0, 4, states.get()); |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, SetFullscreenAndRestore) { |
| // Make sure the window is initialized to normal state from the beginning. |
| EXPECT_EQ(PLATFORM_WINDOW_STATE_NORMAL, window_->GetPlatformWindowState()); |
| |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| |
| AddStateToWlArray(XDG_SURFACE_STATE_FULLSCREEN, states.get()); |
| |
| EXPECT_CALL(*GetXdgSurface(), SetFullscreen()); |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_FULLSCREEN))); |
| window_->ToggleFullscreen(); |
| // Make sure than WaylandWindow manually handles fullscreen states. Check the |
| // comment in the WaylandWindow::ToggleFullscreen. |
| EXPECT_EQ(window_->GetPlatformWindowState(), |
| PLATFORM_WINDOW_STATE_FULLSCREEN); |
| SendConfigureEvent(0, 0, 1, states.get()); |
| Sync(); |
| |
| EXPECT_CALL(*GetXdgSurface(), UnsetFullscreen()); |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_NORMAL))); |
| window_->Restore(); |
| EXPECT_EQ(window_->GetPlatformWindowState(), PLATFORM_WINDOW_STATE_UNKNOWN); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(0, 0, 2, states.get()); |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) { |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| |
| EXPECT_CALL(*GetXdgSurface(), SetMaximized()); |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_MAXIMIZED))); |
| window_->Maximize(); |
| AddStateToWlArray(XDG_SURFACE_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(0, 0, 2, states.get()); |
| Sync(); |
| |
| EXPECT_CALL(*GetXdgSurface(), SetFullscreen()); |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_FULLSCREEN))); |
| window_->ToggleFullscreen(); |
| AddStateToWlArray(XDG_SURFACE_STATE_FULLSCREEN, states.get()); |
| SendConfigureEvent(0, 0, 3, states.get()); |
| Sync(); |
| |
| EXPECT_CALL(*GetXdgSurface(), UnsetFullscreen()); |
| EXPECT_CALL(*GetXdgSurface(), UnsetMaximized()); |
| EXPECT_CALL(delegate_, |
| OnWindowStateChanged(Eq(PLATFORM_WINDOW_STATE_NORMAL))); |
| window_->Restore(); |
| // Reinitialize wl_array, which removes previous old states. |
| states = InitializeWlArrayWithActivatedState(); |
| SendConfigureEvent(0, 0, 4, states.get()); |
| Sync(); |
| } |
| |
| 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_SURFACE_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(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(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(); |
| |
| 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_SURFACE_STATE_FULLSCREEN, states.get()); |
| SendConfigureEvent(fullscreen_bounds.width(), fullscreen_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(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, 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_SURFACE_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(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_SURFACE_STATE_FULLSCREEN, states.get()); |
| SendConfigureEvent(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_SURFACE_STATE_MAXIMIZED, states.get()); |
| SendConfigureEvent(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(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(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(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) { |
| // set_pointer_focus(true) requires a WaylandPointer. |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| window_->set_pointer_focus(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_)); |
| } |
| |
| 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, HasCaptureUpdatedOnPointerEvents) { |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| |
| Sync(); |
| |
| wl::MockPointer* pointer = server_.seat()->pointer(); |
| ASSERT_TRUE(pointer); |
| |
| wl_pointer_send_enter(pointer->resource(), 1, surface_->resource(), 0, 0); |
| Sync(); |
| EXPECT_FALSE(window_->HasCapture()); |
| |
| wl_pointer_send_button(pointer->resource(), 2, 1002, BTN_LEFT, |
| WL_POINTER_BUTTON_STATE_PRESSED); |
| Sync(); |
| EXPECT_TRUE(window_->HasCapture()); |
| |
| wl_pointer_send_motion(pointer->resource(), 1003, wl_fixed_from_int(400), |
| wl_fixed_from_int(500)); |
| Sync(); |
| EXPECT_TRUE(window_->HasCapture()); |
| |
| wl_pointer_send_button(pointer->resource(), 4, 1004, BTN_LEFT, |
| WL_POINTER_BUTTON_STATE_RELEASED); |
| Sync(); |
| EXPECT_FALSE(window_->HasCapture()); |
| } |
| |
| TEST_P(WaylandWindowTest, ConfigureEvent) { |
| ScopedWlArray states; |
| SendConfigureEvent(1000, 1000, 12, states.get()); |
| SendConfigureEvent(1500, 1000, 13, states.get()); |
| |
| // Make sure that the implementation does not call OnBoundsChanged for each |
| // configure event if it receives multiple in a row. |
| EXPECT_CALL(delegate_, OnBoundsChanged(Eq(gfx::Rect(0, 0, 1500, 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, 1500, 1000)).Times(1); |
| EXPECT_CALL(*xdg_surface_, AckConfigure(13)); |
| } |
| |
| 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(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) { |
| EXPECT_FALSE(window_->is_active()); |
| |
| { |
| ScopedWlArray states = InitializeWlArrayWithActivatedState(); |
| EXPECT_CALL(delegate_, OnActivationChanged(Eq(true))); |
| SendConfigureEvent(0, 0, 1, states.get()); |
| Sync(); |
| EXPECT_TRUE(window_->is_active()); |
| } |
| |
| ScopedWlArray states; |
| EXPECT_CALL(delegate_, OnActivationChanged(Eq(false))); |
| SendConfigureEvent(0, 0, 2, states.get()); |
| Sync(); |
| EXPECT_FALSE(window_->is_active()); |
| } |
| |
| TEST_P(WaylandWindowTest, OnAcceleratedWidgetDestroy) { |
| window_.reset(); |
| } |
| |
| TEST_P(WaylandWindowTest, CreateAndDestroyMenuWindow) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, widget_, gfx::Rect(0, 0, 10, 10), |
| &menu_window_delegate); |
| |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, CreateAndDestroyMenuWindowWithFocusedParent) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| |
| // set_pointer_focus(true) requires a WaylandPointer. |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| window_->set_pointer_focus(true); |
| |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, gfx::kNullAcceleratedWidget, |
| gfx::Rect(0, 0, 10, 10), &menu_window_delegate); |
| |
| 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); |
| 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); |
| |
| Sync(); |
| } |
| |
| TEST_P(WaylandWindowTest, CanDispatchEventToMenuWindowNonNested) { |
| MockPlatformWindowDelegate menu_window_delegate; |
| std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams( |
| PlatformWindowType::kMenu, widget_, gfx::Rect(0, 0, 10, 10), |
| &menu_window_delegate); |
| |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| window_->set_pointer_focus(true); |
| |
| // Make sure the events are sent to the menu window despite the pointer focus |
| // on the main window. Typically, it's the menu controller, which must get all |
| // the events in the case like this. |
| EXPECT_FALSE(window_->CanDispatchEvent(&test_mouse_event_)); |
| EXPECT_TRUE(menu_window->CanDispatchEvent(&test_mouse_event_)); |
| |
| menu_window.reset(); |
| } |
| |
| TEST_P(WaylandWindowTest, CanDispatchEventToMenuWindowNested) { |
| 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); |
| |
| 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); |
| |
| Sync(); |
| |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| Sync(); |
| ASSERT_TRUE(connection_->pointer()); |
| window_->set_pointer_focus(true); |
| |
| // In case of nested menu windows, it is the main menu window, which must |
| // receive all the events. |
| EXPECT_FALSE(window_->CanDispatchEvent(&test_mouse_event_)); |
| EXPECT_TRUE(menu_window->CanDispatchEvent(&test_mouse_event_)); |
| EXPECT_FALSE(nested_menu_window->CanDispatchEvent(&test_mouse_event_)); |
| |
| Sync(); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(XdgVersionV5Test, |
| WaylandWindowTest, |
| ::testing::Values(kXdgShellV5)); |
| INSTANTIATE_TEST_CASE_P(XdgVersionV6Test, |
| WaylandWindowTest, |
| ::testing::Values(kXdgShellV6)); |
| |
| } // namespace ui |