// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


#include "ui/ozone/platform/wayland/host/wayland_window.h"

#include <cursor-shape-v1-client-protocol.h>
#include <linux/input.h>
#include <wayland-server-core.h>
#include <xdg-shell-server-protocol.h>

#include <algorithm>
#include <array>
#include <cstddef>
#include <memory>
#include <utility>
#include <vector>

#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/nix/xdg_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_command_line.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/hit_test.h"
#include "ui/base/owned_window_anchor.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_types.h"
#include "ui/display/display.h"
#include "ui/display/scoped_display_for_new_windows.h"
#include "ui/display/test/test_screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/gfx/overlay_plane_data.h"
#include "ui/gfx/overlay_priority_hint.h"
#include "ui/gfx/overlay_transform.h"
#include "ui/ozone/common/bitmap_cursor.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
#include "ui/ozone/platform/wayland/common/wayland_util.h"
#include "ui/ozone/platform/wayland/host/wayland_async_cursor.h"
#include "ui/ozone/platform/wayland/host/wayland_buffer_handle.h"
#include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor_shape.h"
#include "ui/ozone/platform/wayland/host/wayland_event_source.h"
#include "ui/ozone/platform/wayland/host/wayland_output.h"
#include "ui/ozone/platform/wayland/host/wayland_output_manager.h"
#include "ui/ozone/platform/wayland/host/wayland_seat.h"
#include "ui/ozone/platform/wayland/host/wayland_subsurface.h"
#include "ui/ozone/platform/wayland/host/wayland_toplevel_window.h"
#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom.h"
#include "ui/ozone/platform/wayland/test/mock_pointer.h"
#include "ui/ozone/platform/wayland/test/mock_surface.h"
#include "ui/ozone/platform/wayland/test/mock_wayland_platform_window_delegate.h"
#include "ui/ozone/platform/wayland/test/mock_xdg_surface.h"
#include "ui/ozone/platform/wayland/test/scoped_wl_array.h"
#include "ui/ozone/platform/wayland/test/test_keyboard.h"
#include "ui/ozone/platform/wayland/test/test_output.h"
#include "ui/ozone/platform/wayland/test/test_region.h"
#include "ui/ozone/platform/wayland/test/test_touch.h"
#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h"
#include "ui/ozone/platform/wayland/test/wayland_connection_test_api.h"
#include "ui/ozone/platform/wayland/test/wayland_test.h"
#include "ui/ozone/public/ozone_switches.h"
#include "ui/platform_window/platform_window.h"
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/platform_window/wm/wm_move_resize_handler.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::InvokeWithoutArgs;
using ::testing::Mock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::StrEq;
using ::testing::Values;

namespace ui {

namespace {

constexpr float kDefaultCursorScale = 1.f;

struct PopupPosition {
  gfx::Rect anchor_rect;
  gfx::Size size;
  uint32_t anchor = 0;
  uint32_t gravity = 0;
  uint32_t constraint_adjustment = 0;
};

base::ScopedFD MakeFD() {
  base::FilePath temp_path;
  EXPECT_TRUE(base::CreateTemporaryFile(&temp_path));
  auto file =
      base::File(temp_path, base::File::FLAG_READ | base::File::FLAG_WRITE |
                                base::File::FLAG_CREATE_ALWAYS);
  return base::ScopedFD(file.TakePlatformFile());
}

// Must happen on the server thread.
wl::TestXdgPopup* GetTestXdgPopupByWindow(wl::TestWaylandServerThread* server,
                                          const uint32_t surface_id) {
  DCHECK(server->task_runner()->BelongsToCurrentThread());
  wl::MockSurface* mock_surface =
      server->GetObject<wl::MockSurface>(surface_id);
  if (mock_surface) {
    auto* mock_xdg_surface = mock_surface->xdg_surface();
    if (mock_xdg_surface) {
      return mock_xdg_surface->xdg_popup();
    }
  }
  return nullptr;
}

void AddStateToWlArray(uint32_t state, wl_array* states) {
  *static_cast<uint32_t*>(wl_array_add(states, sizeof state)) = state;
}

wl::ScopedWlArray InitializeWlArrayWithActivatedState() {
  return wl::ScopedWlArray({XDG_TOPLEVEL_STATE_ACTIVATED});
}

wl::ScopedWlArray MakeStateArray(const std::vector<int32_t> states) {
  return wl::ScopedWlArray(states);
}

class MockCursorShape : public WaylandCursorShape {
 public:
  MockCursorShape() : WaylandCursorShape(nullptr, nullptr) {}
  MockCursorShape(const MockCursorShape&) = delete;
  MockCursorShape& operator=(const MockCursorShape&) = delete;
  ~MockCursorShape() override = default;

  MOCK_METHOD(void, SetCursorShape, (uint32_t), (override));
};

using BoundsChange = PlatformWindowDelegate::BoundsChange;

constexpr BoundsChange kDefaultBoundsChange{false};

scoped_refptr<PlatformCursor> AsPlatformCursor(
    scoped_refptr<BitmapCursor> bitmap_cursor) {
  return base::MakeRefCounted<WaylandAsyncCursor>(bitmap_cursor);
}

using DispatchEventCallback = base::OnceCallback<void(Event*)>;

class TestWaylandWindowDelegate : public PlatformWindowDelegate {
 public:
  void SetDispatchEventCallback(DispatchEventCallback callback) {
    callback_ = std::move(callback);
  }

  // ui::PlatformWindowDelegate implementation.
  void OnBoundsChanged(const BoundsChange& change) override {}
  void OnDamageRect(const gfx::Rect& damaged_region) override {}
  void OnCloseRequest() override {}
  void OnClosed() override {}
  void OnWindowStateChanged(ui::PlatformWindowState old_state,
                            ui::PlatformWindowState new_state) override {}
  void OnLostCapture() override {}
  void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override {}
  void OnWillDestroyAcceleratedWidget() override {}
  void OnAcceleratedWidgetDestroyed() override {}
  void OnActivationChanged(bool active) override {}
  void OnCursorUpdate() override {}
  void DispatchEvent(Event* event) override { std::move(callback_).Run(event); }

 private:
  DispatchEventCallback callback_;
};

}  // namespace

class WaylandWindowTest : public WaylandTest {
 public:
  WaylandWindowTest()
      : test_mouse_event_(EventType::kMousePressed,
                          gfx::Point(10, 15),
                          gfx::Point(10, 15),
                          ui::EventTimeStampFromSeconds(123456),
                          EF_LEFT_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON,
                          EF_LEFT_MOUSE_BUTTON) {}

  WaylandWindowTest(const WaylandWindowTest&) = delete;
  WaylandWindowTest& operator=(const WaylandWindowTest&) = delete;

  void SetUp() override {
    WaylandTest::SetUp();

    buffer_id_gen_ = 0u;
    frame_id_gen_ = 0u;
    surface_id_ = window_->root_surface()->get_surface_id();
    PostToServerAndWait(
        [id = surface_id_](wl::TestWaylandServerThread* server) {
          auto* surface = server->GetObject<wl::MockSurface>(id);
          ASSERT_TRUE(surface->xdg_surface());
        });
  }

 protected:
  void SendConfigureEventPopup(WaylandWindow* menu_window,
                               const gfx::Rect& bounds) {
    const uint32_t surface_id = menu_window->root_surface()->get_surface_id();
    PostToServerAndWait(
        [surface_id, bounds](wl::TestWaylandServerThread* server) {
          auto* popup = GetTestXdgPopupByWindow(server, surface_id);
          ASSERT_TRUE(popup);
          xdg_popup_send_configure(popup->resource(), bounds.x(), bounds.y(),
                                   bounds.width(), bounds.height());
        });
  }

  // Simulates up to date buffers coming through viz and being latched.
  // Call this after configures or anything where you want wayland or latched
  // state to update.
  void AdvanceFrameToCurrent(
      WaylandWindow* window,
      const MockWaylandPlatformWindowDelegate& delegate) {
    AdvanceFrameToGivenVizSequenceId(window, delegate, delegate.viz_seq());
  }

  void AdvanceFrameToGivenVizSequenceId(
      WaylandWindow* window,
      const MockWaylandPlatformWindowDelegate& delegate,
      int64_t viz_seq) {
    WaylandTestBase::SyncDisplay();
    window->OnSequencePoint(viz_seq);
    window->root_surface()->ApplyPendingState();
  }

  void InitializeWithSupportedHitTestValues(std::vector<int>* hit_tests) {
    hit_tests->push_back(static_cast<int>(HTBOTTOM));
    hit_tests->push_back(static_cast<int>(HTBOTTOMLEFT));
    hit_tests->push_back(static_cast<int>(HTBOTTOMRIGHT));
    hit_tests->push_back(static_cast<int>(HTLEFT));
    hit_tests->push_back(static_cast<int>(HTRIGHT));
    hit_tests->push_back(static_cast<int>(HTTOP));
    hit_tests->push_back(static_cast<int>(HTTOPLEFT));
    hit_tests->push_back(static_cast<int>(HTTOPRIGHT));
  }

  MockCursorShape* InstallMockCursorShape() {
    auto mock_cursor_shapes = std::make_unique<MockCursorShape>();
    MockCursorShape* mock_cursor_shapes_ptr = mock_cursor_shapes.get();
    WaylandConnectionTestApi test_api(connection_.get());
    test_api.SetCursorShape(std::move(mock_cursor_shapes));
    return mock_cursor_shapes_ptr;
  }

  // Verifies and clearis expectations for a toplevel window associated with
  // `delegate` and whose root surface id is `surface_id`. Both client and
  // server-side expectations are checked, including xdg-toplevel as well as
  // wp-viewport.
  void VerifyAndClearExpectations(MockWaylandPlatformWindowDelegate& delegate,
                                  uint32_t surface_id) {
    // Client side verification.
    Mock::VerifyAndClearExpectations(&delegate);

    // Server side verification.
    // `PostToServerAndWait` runs `RoundTripQueue` to wait for the queue to be
    // empty. It makes sure that `VerifyAndClearExpectations` below will run
    // after all requests had been handled.
    PostToServerAndWait([id = surface_id](wl::TestWaylandServerThread* server) {
      wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
      ASSERT_TRUE(mock_surface);
      wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();

      // Verify the expectations for mock objects.
      Mock::VerifyAndClearExpectations(mock_surface);
      Mock::VerifyAndClearExpectations(xdg_surface);
      Mock::VerifyAndClearExpectations(xdg_surface->xdg_toplevel());
      Mock::VerifyAndClearExpectations(mock_surface->viewport());
    });
  }

  // Verifies expectations for `window_` on both client and server. It does not
  // handle all expectations, so more calls to Mock::VerifyAndClearExpectations
  // for other associated mock objects not listed in this method might be
  // needed.
  //
  // Note: It is not required to call this at the end of the test to verify the
  // expectations.
  void VerifyAndClearExpectations() {
    VerifyAndClearExpectations(delegate_, surface_id_);
  }

  void DoMinimizeTest(bool async_state);
  void DoSetFullscreenAndRestoreTest(bool async_state);
  void DoStartWithFullscreenTest(bool async_state);
  void DoStartMaximizedTest(bool async_state);
  void DoStartWithMinimizedTest(bool async_state);
  void DoSetMaximizedFullscreenAndRestoreTest(bool async_state);

  void VerifyXdgPopupPosition(WaylandWindow* menu_window,
                              const PopupPosition& position) {
    const uint32_t surface_id = menu_window->root_surface()->get_surface_id();
    PostToServerAndWait([surface_id,
                         position](wl::TestWaylandServerThread* server) {
      auto* popup = GetTestXdgPopupByWindow(server, surface_id);
      ASSERT_TRUE(popup);

      EXPECT_EQ(popup->anchor_rect(), position.anchor_rect);
      EXPECT_EQ(popup->size(), position.size);
      EXPECT_EQ(popup->anchor(), position.anchor);
      EXPECT_EQ(popup->gravity(), position.gravity);
      EXPECT_EQ(popup->constraint_adjustment(), position.constraint_adjustment);
    });
  }

  void VerifyCanDispatchMouseEvents(
      WaylandWindow* dispatching_window,
      const std::vector<WaylandWindow*>& non_dispatching_windows) {
    auto* pointer_focused_window =
        connection_->window_manager()->GetCurrentPointerFocusedWindow();

    ASSERT_TRUE(pointer_focused_window);
    Event::DispatcherApi(&test_mouse_event_).set_target(pointer_focused_window);
    EXPECT_TRUE(dispatching_window->CanDispatchEvent(&test_mouse_event_));
    for (auto* window : non_dispatching_windows) {
      EXPECT_FALSE(window->CanDispatchEvent(&test_mouse_event_));
    }
  }

  void VerifyCanDispatchTouchEvents(
      const std::vector<WaylandWindow*>& dispatching_windows,
      const std::vector<WaylandWindow*>& non_dispatching_windows) {
    ASSERT_LT(dispatching_windows.size(), 2u);
    auto* touch_focused_window =
        connection_->window_manager()->GetCurrentTouchFocusedWindow();
    // There must be focused window to dispatch.
    if (dispatching_windows.size() == 0) {
      EXPECT_FALSE(touch_focused_window);
    }

    PointerDetails pointer_details(EventPointerType::kTouch, 1);
    TouchEvent test_touch_event(EventType::kTouchPressed, {1, 1},
                                base::TimeTicks(), pointer_details);
    if (touch_focused_window) {
      Event::DispatcherApi(&test_touch_event).set_target(touch_focused_window);
    }
    for (auto* window : dispatching_windows) {
      EXPECT_TRUE(window->CanDispatchEvent(&test_touch_event));
    }
    for (auto* window : non_dispatching_windows) {
      // Make sure that the CanDispatcEvent works on release build.
#if DCHECK_IS_ON()
      // Disable DCHECK when enabled.
      window->disable_null_target_dcheck_for_testing();
#endif
      EXPECT_FALSE(window->CanDispatchEvent(&test_touch_event));
    }
  }

  void VerifyCanDispatchKeyEvents(
      const std::vector<WaylandWindow*>& dispatching_windows,
      const std::vector<WaylandWindow*>& non_dispatching_windows) {
    ASSERT_LT(dispatching_windows.size(), 2u);
    auto* keyboard_focused_window =
        connection_->window_manager()->GetCurrentKeyboardFocusedWindow();

    // There must be focused window to dispatch.
    if (dispatching_windows.size() == 0) {
      EXPECT_FALSE(keyboard_focused_window);
    }

    KeyEvent test_key_event(EventType::kKeyPressed, VKEY_0, 0);
    if (keyboard_focused_window) {
      Event::DispatcherApi(&test_key_event).set_target(keyboard_focused_window);
    }

    for (auto* window : dispatching_windows) {
      EXPECT_TRUE(window->CanDispatchEvent(&test_key_event));
    }
    for (auto* window : non_dispatching_windows) {
      // Make sure that the CanDispatcEvent works on release build.
#if DCHECK_IS_ON()
      // Disable DCHECK when enabled.
      window->disable_null_target_dcheck_for_testing();
#endif
      EXPECT_FALSE(window->CanDispatchEvent(&test_key_event));
    }
  }

  uint32_t GetObjIdForOutput(WaylandOutput::Id id) {
    auto* output_manager = connection_->wayland_output_manager();
    auto* wayland_output = output_manager->GetOutput(id);
    if (wayland_output) {
      return wl_proxy_get_id(
          reinterpret_cast<wl_proxy*>(wayland_output->get_output()));
    }
    return 0;
  }

  WaylandBufferHandle* CreateTestShmBuffer(const gfx::Size& buffer_size,
                                           WaylandSurface* surface) {
    CHECK(!buffer_size.IsEmpty());
    CHECK(surface);
    CHECK(connection_->buffer_manager_host());

    auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
    buffer_manager_gpu_->Initialize(std::move(interface_ptr), {},
                                    /*supports_dma_buf=*/false,
                                    /*supports_viewporter=*/true,
                                    /*supports_acquire_fence=*/false,
                                    /*supports_overlays=*/true,
                                    /*supports_single_pixel_buffer=*/true);

    const uint32_t buffer_id = ++buffer_id_gen_;
    auto length = buffer_size.width() * buffer_size.height() * 4;
    buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
                                              buffer_id);
    task_environment_.RunUntilIdle();
    return connection_->buffer_manager_host()->EnsureBufferHandle(surface,
                                                                  buffer_id);
  }

  // Emulates a new frame being received from Viz being processed by `window`s
  // frame manager.
  void CreateBufferAndPresentAsNewFrame(
      WaylandWindow* window,
      const MockWaylandPlatformWindowDelegate& delegate,
      const gfx::Size& buffer_size,
      float buffer_scale) {
    CHECK(window);
    CHECK(window->root_surface());
    auto* buffer = CreateTestShmBuffer(buffer_size, window->root_surface());
    ASSERT_TRUE(buffer);
    window->root_surface()->AttachBuffer(buffer);

    const uint32_t frame_id = ++frame_id_gen_;
    wl::WaylandOverlayConfig root_config;
    root_config.buffer_id = buffer->id();
    root_config.bounds_rect = gfx::RectF(buffer_size);
    root_config.damage_region = gfx::Rect(buffer_size);
    root_config.surface_scale_factor = buffer_scale;
    std::vector<wl::WaylandOverlayConfig> configs;
    configs.push_back(std::move(root_config));
    window->CommitOverlays(frame_id, gfx::FrameData(delegate.viz_seq()),
                           configs);
  }

  // Surface id of |window|'s the root surface. Stored for convenience.
  uint32_t surface_id_ = 0u;

  MouseEvent test_mouse_event_;

  // Incremental id used to generate buffer IDs.
  uint32_t buffer_id_gen_ = 0u;
  // Incremental id used to generate frame IDs.
  uint32_t frame_id_gen_ = 0u;
};

// Regression test for crbug.com/1433175
TEST_P(WaylandWindowTest, Shutdown) {
  window_->PrepareForShutdown();
  window_->OnDragSessionClose(mojom::DragOperation::kNone);
}

// Regression test for https://crbug.com/495948109.
TEST_P(WaylandWindowTest, DeleteWindowFromOnStateUpdate) {
  delegate_.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
    window_.reset();
    return false;
  }));

  window_->SetBoundsInDIP(gfx::Rect(1024, 768));
}

// Regression test for https://crbug.com/495948109.
TEST_P(WaylandWindowTest, DeleteWindowFromOnStateUpdateDuringSurfaceConfigure) {
  delegate_.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
    window_.reset();
    return false;
  }));

  WaylandWindow* window = window_.get();
  WaylandWindow::WindowStates window_states;
  window_states.is_activated = true;
  window->HandleToplevelConfigure(1024, 768, window_states);
  window->HandleSurfaceConfigure(2);

  EXPECT_FALSE(window_);
}

TEST_P(WaylandWindowTest, SetTitle) {
  window_->SetTitle(u"hello");
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(surface);
    EXPECT_EQ("hello", surface->xdg_surface()->xdg_toplevel()->title());
  });
}

TEST_P(WaylandWindowTest, OnSequencePointConfiguresWaylandWindow) {
  constexpr gfx::Rect kNormalBounds{500, 300};

  // Configure event makes Wayland update bounds, but does not change toplevel
  // input region, opaque region or window geometry immediately. Such actions
  // are postponed to OnSequencePoint();
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(0);
    EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
  });

  auto state = InitializeWlArrayWithActivatedState();
  constexpr uint32_t kConfigureSerial = 2u;
  SendConfigureEvent(surface_id_, kNormalBounds.size(), state,
                     kConfigureSerial);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
    EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial));
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
    EXPECT_CALL(*mock_surface, SetInputRegion(_));
  });
  AdvanceFrameToCurrent(window_.get(), delegate_);
}

// WaylandSurface state changes are sent to wayland compositor when
// ApplyPendingState() is called.
TEST_P(WaylandWindowTest, ApplyPendingStatesAndCommit) {
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    // Set*() calls do not send wl_surface requests.
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
  });

  std::vector<gfx::Rect> region_px = {gfx::Rect{500, 300}};
  window_->root_surface()->set_opaque_region(region_px);
  window_->root_surface()->set_input_region(region_px);
  window_->root_surface()->set_surface_buffer_scale(2);
  VerifyAndClearExpectations();

  WaylandTestBase::SyncDisplay();

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    // ApplyPendingState() generates wl_surface requests and Commit() causes a
    // wayland connection flush.
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
    EXPECT_CALL(*mock_surface, Commit()).Times(1);
  });

  window_->root_surface()->ApplyPendingState();
  window_->root_surface()->Commit();
  VerifyAndClearExpectations();

  WaylandTestBase::SyncDisplay();
}

// Checks that when the window gets some of its edges tiled, it notifies the
// delegate appropriately.
TEST_P(WaylandWindowTest, HandleTiledEdges) {
  constexpr gfx::Rect kWindowBounds{800, 600};

  struct {
    std::vector<xdg_toplevel_state> configured_states;
    WindowTiledEdges expected_tiled_edges;
  } kTestCases[]{
      {{XDG_TOPLEVEL_STATE_TILED_LEFT}, {true, false, false, false}},
      {{XDG_TOPLEVEL_STATE_TILED_RIGHT}, {false, true, false, false}},
      {{XDG_TOPLEVEL_STATE_TILED_TOP}, {false, false, true, false}},
      {{XDG_TOPLEVEL_STATE_TILED_BOTTOM}, {false, false, false, true}},
      {{XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_TOP},
       {true, false, true, false}},
      {{XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_BOTTOM},
       {true, false, false, true}},
      {{XDG_TOPLEVEL_STATE_TILED_RIGHT, XDG_TOPLEVEL_STATE_TILED_TOP},
       {false, true, true, false}},
      {{XDG_TOPLEVEL_STATE_TILED_RIGHT, XDG_TOPLEVEL_STATE_TILED_BOTTOM},
       {false, true, false, true}},
      {{XDG_TOPLEVEL_STATE_TILED_LEFT, XDG_TOPLEVEL_STATE_TILED_TOP,
        XDG_TOPLEVEL_STATE_TILED_BOTTOM},
       {true, false, true, true}},
      {{XDG_TOPLEVEL_STATE_TILED_RIGHT, XDG_TOPLEVEL_STATE_TILED_TOP,
        XDG_TOPLEVEL_STATE_TILED_BOTTOM},
       {false, true, true, true}},
  };
  for (const auto& test_case : kTestCases) {
    auto configured_states = InitializeWlArrayWithActivatedState();
    for (const auto additional_state : test_case.configured_states) {
      AddStateToWlArray(additional_state, configured_states.get());
    }

    EXPECT_CALL(delegate_,
                OnWindowTiledStateChanged(test_case.expected_tiled_edges))
        .Times(1);
    SendConfigureEvent(surface_id_, kWindowBounds.size(), configured_states);

    VerifyAndClearExpectations();
  }
}

TEST_P(WaylandWindowTest, DisregardUnpassedWindowConfigure) {
  constexpr gfx::Rect kNormalBounds1{500, 300};
  constexpr gfx::Rect kNormalBounds2{800, 600};
  constexpr gfx::Rect kNormalBounds3{700, 400};
  uint32_t serial = 1;

  // Send 3 configures, and skip OnSequencePoint for the result of the second
  // configure. The second configure should not be acked or have its properties
  // applied.
  PostToServerAndWait(
      [id = surface_id_, bounds1 = kNormalBounds1, bounds2 = kNormalBounds2,
       bounds3 = kNormalBounds3](wl::TestWaylandServerThread* server) {
        wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(surface);
        wl::MockXdgSurface* xdg_surface = surface->xdg_surface();

        EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds1.size())));
        EXPECT_CALL(*xdg_surface, AckConfigure(2));
        EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds2.size())))
            .Times(0);
        EXPECT_CALL(*xdg_surface, AckConfigure(3)).Times(0);
        EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds3.size())));
        EXPECT_CALL(*xdg_surface, AckConfigure(4));
      });

  auto state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds1.size(), state, ++serial);
  state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds2.size(), state, ++serial);
  state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds3.size(), state, ++serial);

  window_->OnSequencePoint(/*seq=*/1);
  window_->OnSequencePoint(/*seq=*/3);
  VerifyAndClearExpectations();
}

TEST_P(WaylandWindowTest, MismatchedSequencePoints) {
  constexpr gfx::Rect kNormalBounds1{500, 300};
  constexpr gfx::Rect kNormalBounds2{800, 600};
  constexpr gfx::Rect kNormalBounds3{700, 400};

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();

    // OnSequencePoint with mismatched sequence points from configure
    // events does not acknowledge toplevel configure.
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
    EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
  });

  auto state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds1.size(), state);
  state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds2.size(), state);
  state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds3.size(), state);
  // Needs sequence point > 0 to latch.
  window_->OnSequencePoint(0);
  VerifyAndClearExpectations();
}

// Regression test for bugs like crbug.com/413007181 and crbug.com/340363673.
TEST_P(WaylandWindowTest, GeometrySentOnTilingStateChange) {
  constexpr gfx::Rect kBounds{800, 600};

  // Make sure the window has only active state initially.
  auto active = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED});
  SendConfigureEvent(surface_id_, {0, 0}, active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Now add tiled state and check that the geometry is sent.
  auto active_tiled = MakeStateArray(
      {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_TILED_LEFT});
  PostToServerAndWait([id = surface_id_,
                       bounds = kBounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
    EXPECT_CALL(*xdg_surface, AckConfigure(_));
  });
  SendConfigureEvent(surface_id_, kBounds.size(), active_tiled);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Now remove tiled state and check that the geometry is sent.
  PostToServerAndWait([id = surface_id_,
                       bounds = kBounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
    EXPECT_CALL(*xdg_surface, AckConfigure(_));
  });
  SendConfigureEvent(surface_id_, kBounds.size(), active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();
}

TEST_P(WaylandWindowTest, OnSequencePointClearsPreviousUnackedConfigures) {
  constexpr gfx::Rect kNormalBounds1{500, 300};
  constexpr gfx::Rect kNormalBounds2{800, 600};
  constexpr gfx::Rect kNormalBounds3{700, 400};
  uint32_t serial = 1;
  auto state = InitializeWlArrayWithActivatedState();

  // Send 3 configures. Waiting to advance the frame (and call
  // OnSequencePoint(3)) should mean acking and processing the completion of
  // first two configures will be skipped.
  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds1](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(0);
    EXPECT_CALL(*xdg_surface, AckConfigure(2)).Times(0);
  });
  SendConfigureEvent(surface_id_, kNormalBounds1.size(), state, ++serial);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds2](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(0);
    EXPECT_CALL(*xdg_surface, AckConfigure(3)).Times(0);
  });
  state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds2.size(), state, ++serial);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds3](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
    EXPECT_CALL(*xdg_surface, AckConfigure(4));
  });
  state = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds3.size(), state, ++serial);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();
}

// This test used to specifically to guard against origin being set to (0, 0)
// thus lacros could be restored to correct display (crbug.com/1423690).
// TODO(crbug.com/374244479) Given lacros has been deprecated, update this test
// to ignore the origin and only verify the size as origin is not known in
// Wayland world.
TEST_P(WaylandWindowTest, RestoredBoundsSetWithCorrectOrigin) {
  constexpr gfx::Rect kNormalBounds{1376, 10, 500, 300};
  constexpr gfx::Rect kMaximizedBounds{1366, 0, 800, 600};

  // Make sure the window has normal state initially.
  window_->SetBoundsInDIP(kNormalBounds);
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Deactivate the surface.
  auto empty_state = MakeStateArray({});
  SendConfigureEvent(surface_id_, {0, 0}, empty_state);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  WaylandWindow::WindowStates window_states;
  window_states.is_maximized = true;
  window_states.is_activated = true;

  window_->HandleToplevelConfigure(kMaximizedBounds.width(),
                                   kMaximizedBounds.height(), window_states);
  window_->HandleSurfaceConfigure(2);

  EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
  EXPECT_EQ(window_->GetRestoredBoundsInDIP(), kNormalBounds);
}

TEST_P(WaylandWindowTest, MaximizeAndRestore) {
  constexpr gfx::Rect kNormalBounds{500, 300};
  constexpr gfx::Rect kMaximizedBounds{800, 600};

  // Make sure the window has normal state initially.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Deactivate the surface.
  auto empty_state = MakeStateArray({});
  SendConfigureEvent(surface_id_, {0, 0}, empty_state);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  auto active_maximized = MakeStateArray(
      {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
  PostToServerAndWait([id = surface_id_, bounds = kMaximizedBounds](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  // Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
  // the real delegate.  Its OnWindowStateChanged() may (through some chain of
  // calls) invoke SetWindowGeometry(), but that should not happen during the
  // change of the window state.
  // See https://crbug.com/1223005.
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  window_->Maximize();
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED});
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), inactive_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
  });
  // Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
  // the real delegate.  Its OnWindowStateChanged() may (through some chain of
  // calls) invoke SetWindowGeometry(), but that should not happen during the
  // change of the window state.
  // See https://crbug.com/1223005.
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetMaximized());
  });
  window_->Restore();
  // Reinitialize wl_array, which removes previous old states.
  auto active = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();
}

TEST_P(WaylandWindowTest, MaximizeAndRestoreWithInsets) {
  constexpr gfx::Rect kNormalBounds{510, 310};
  constexpr gfx::Insets kNormalInsets(5);

  constexpr gfx::Rect kMaximizedBounds{800, 600};
  constexpr gfx::Insets kMaximizedInsets(0);

  // Make sure the window has normal state initially.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Deactivate the surface.
  auto empty_state = MakeStateArray({});
  SendConfigureEvent(surface_id_, {0, 0}, empty_state);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  auto maximized_geometry = kMaximizedBounds;
  maximized_geometry.Inset(kMaximizedInsets);
  auto active_maximized = MakeStateArray(
      {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
  PostToServerAndWait([id = surface_id_, bounds = maximized_geometry](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  // Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
  // the real delegate.  Its OnWindowStateChanged() may (through some chain of
  // calls) invoke SetWindowGeometry(), but that should not happen during the
  // change of the window state.
  // See https://crbug.com/1223005.
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
      .WillRepeatedly(Return(kMaximizedInsets));
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  window_->Maximize();
  SendConfigureEvent(surface_id_, maximized_geometry.size(), active_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED});
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
      .WillRepeatedly(Return(kMaximizedInsets));
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  SendConfigureEvent(surface_id_, maximized_geometry.size(),
                     inactive_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
      .WillRepeatedly(Return(kMaximizedInsets));
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  SendConfigureEvent(surface_id_, maximized_geometry.size(), active_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  auto normal_geometry = kNormalBounds;
  normal_geometry.Inset(kNormalInsets);
  // Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
  // the real delegate.  Its OnWindowStateChanged() may (through some chain of
  // calls) invoke SetWindowGeometry(), but that should not happen during the
  // change of the window state.
  // See https://crbug.com/1223005.
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
      .WillRepeatedly(Return(kNormalInsets));
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  PostToServerAndWait([id = surface_id_, bounds = normal_geometry](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetMaximized());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
  });
  window_->Restore();
  // Reinitialize wl_array, which removes previous old states.
  auto active = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();
}

void WaylandWindowTest::DoMinimizeTest(bool async_state) {
  wl::ScopedWlArray states({});

  // Make sure the window is initialized to normal state from the beginning.
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  SendConfigureEvent(surface_id_, {0, 0}, states);

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMinimized());
  });

  if (async_state) {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
    window_->Minimize();
    VerifyAndClearExpectations();

    // The state remains normal until we receive a configure event.
    EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());

    // Reinitialize wl_array, which removes previous old states.
    states = wl::ScopedWlArray({});
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
    SendConfigureEvent(surface_id_, {800, 600}, states);
    AdvanceFrameToCurrent(window_.get(), delegate_);
  } else {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
    window_->Minimize();
    VerifyAndClearExpectations();

    // Reinitialize wl_array, which removes previous old states.
    states = wl::ScopedWlArray({});
    SendConfigureEvent(surface_id_, {800, 600}, states);
  }

  // Wayland compositor doesn't notify clients about minimized state, but rather
  // if a window is not activated. Thus, a WaylandToplevelWindow marks itself as
  // being minimized and and sets state to minimized. Thus, the state mustn't
  // change after the configuration event is sent.
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);

  // Send one additional empty configuration event (which means the surface is
  // not maximized, fullscreen or activated) to ensure, WaylandWindow stays in
  // the same minimized state, but the delegate is always notified.
  //
  // TODO(tonikito): Improve filtering of delegate notification here.
  ui::PlatformWindowState state;
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _))
      .WillRepeatedly(DoAll(SaveArg<0>(&state), InvokeWithoutArgs([&]() {
                              EXPECT_EQ(state, PlatformWindowState::kMinimized);
                            })));
  SendConfigureEvent(surface_id_, {0, 0}, states);

  // And one last time to ensure the behaviour.
  SendConfigureEvent(surface_id_, {0, 0}, states);
}

TEST_P(WaylandWindowTest, Minimize) {
  DoMinimizeTest(/*async_state=*/false);
}

TEST_P(WaylandWindowTest, MinimizeAsync) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kAsyncFullscreenWindowState);
  DoMinimizeTest(/*async_state=*/true);
}

// Tests the event sequence where a toplevel window is minimized and a restore
// event is initiated by the server.
TEST_P(WaylandWindowTest, ServerInitiatedRestoreFromMinimizedState) {
  wl::ScopedWlArray states({});

  // Make sure the window is initialized to the normal state from the beginning.
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  SendConfigureEvent(surface_id_, {0, 0}, states);
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());

  // A subsequent minimize event from the server should notify delegates of the
  // window state change.
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  {
    WaylandWindow::WindowStates window_states;
    window_states.is_minimized = true;
    window_->HandleToplevelConfigureWithOrigin(0, 0, 0, 0, window_states);
  }
  window_->HandleSurfaceConfigure(3);
  EXPECT_EQ(PlatformWindowState::kMinimized, window_->GetPlatformWindowState());

  // If the minimized state is not supported, the server initiated restore
  // event with no window activation should not restore the window. It should
  // instead leave the window in the minimized state.
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
  window_->HandleToplevelConfigureWithOrigin(0, 0, 0, 0, {});
  window_->HandleSurfaceConfigure(4);
  EXPECT_EQ(PlatformWindowState::kMinimized, window_->GetPlatformWindowState());
}

// Regression test for crbug.com/396148609. A minimized window is not visible,
// so its window geometry and bounds must be left at their restored values.
// Otherwise the geometry origin flips to (0,0) and the bounds shrink to the
// no-shadow size while minimized; on un-minimize the retained (shadowed) buffer
// is briefly shown mismatched against the collapsed anchor, and the window (and
// its web contents) visibly jump before snapping back (Wayland-only).
TEST_P(WaylandWindowTest, MinimizedWindowDoesNotReconfigureGeometry) {
  const auto kInsets = gfx::Insets::TLBR(10, 16, 32, 16);
  // The restored (normal) state reserves the decoration shadow insets; every
  // other state (incl. minimized) reports no insets, matching the browser host.
  auto set_inset_expectations = [&]() {
    EXPECT_CALL(delegate_, CalculateInsetsInDIP(_))
        .WillRepeatedly(Return(gfx::Insets()));
    EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
        .WillRepeatedly(Return(kInsets));
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _))
        .Times(testing::AnyNumber());
  };

  // Drive the window into normal/activated state and latch its restored bounds
  // (which include the decoration shadow) and the content size the compositor
  // reports back via configure events.
  set_inset_expectations();
  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  const gfx::Rect restored_bounds = window_->GetBoundsInDIP();
  const gfx::Size content_size(
      restored_bounds.width() - (kInsets.left() + kInsets.right()),
      restored_bounds.height() - (kInsets.top() + kInsets.bottom()));
  VerifyAndClearExpectations();

  // Minimize. No window geometry must be pushed for the invisible window, and
  // the bounds must not change.
  set_inset_expectations();
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMinimized());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  window_->Minimize();
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);

  // The compositor does not report a minimized state; it only deactivates the
  // toplevel and keeps configuring it at the content size. The window must stay
  // minimized and must keep its restored bounds (no shrinking).
  states = wl::ScopedWlArray({});
  SendConfigureEvent(surface_id_, content_size, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);
  EXPECT_EQ(window_->GetBoundsInDIP(), restored_bounds);
  VerifyAndClearExpectations();

  // Un-minimize: the geometry returns to exactly its restored value, with no
  // intermediate (0,0)-origin flip, and the bounds are unchanged.
  set_inset_expectations();
  PostToServerAndWait(
      [id = surface_id_, insets = kInsets,
       content = content_size](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(
                                      insets.left(), insets.top(),
                                      content.width(), content.height())));
      });
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, content_size, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
  EXPECT_EQ(window_->GetBoundsInDIP(), restored_bounds);
  VerifyAndClearExpectations();
}

void WaylandWindowTest::DoSetFullscreenAndRestoreTest(bool async_state) {
  constexpr gfx::Rect kNormalBounds{500, 300};
  constexpr gfx::Rect kFullscreenBounds{800, 600};

  // Make sure the window is initialized to normal state from the beginning.
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());

  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetFullscreen());
  });

  if (async_state) {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
    window_->SetFullscreen(true, display::kInvalidDisplayId);
    VerifyAndClearExpectations();

    EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());

    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  } else {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
    window_->SetFullscreen(true, display::kInvalidDisplayId);
    // Make sure than WaylandWindow manually handles fullscreen states. Check
    // the comment in the WaylandWindow::SetFullscreen.
    VerifyAndClearExpectations();

    EXPECT_EQ(window_->GetPlatformWindowState(),
              PlatformWindowState::kFullScreen);

    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
  }
  SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  EXPECT_EQ(window_->GetPlatformWindowState(),
            PlatformWindowState::kFullScreen);

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetFullscreen());
  });

  if (async_state) {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
    window_->Restore();
    VerifyAndClearExpectations();

    // The state of the window must remain fullscreen until the Configure event.
    EXPECT_EQ(window_->GetPlatformWindowState(),
              PlatformWindowState::kFullScreen);

    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
    // Reinitialize wl_array, which removes previous old states.
    states = InitializeWlArrayWithActivatedState();
    SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
    AdvanceFrameToCurrent(window_.get(), delegate_);
  } else {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
    window_->Restore();
    VerifyAndClearExpectations();

    EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);

    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
    // Reinitialize wl_array, which removes previous old states.
    states = InitializeWlArrayWithActivatedState();
    SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
  }

  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
}

TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
  DoSetFullscreenAndRestoreTest(/*async_state=*/false);
}

TEST_P(WaylandWindowTest, SetFullscreenAndRestoreAsync) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kAsyncFullscreenWindowState);
  DoSetFullscreenAndRestoreTest(/*async_state=*/true);
}

void WaylandWindowTest::DoStartWithFullscreenTest(bool async_state) {
  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(100, 100);
  properties.type = PlatformWindowType::kWindow;
  // We need to create a window avoid calling Show() on it as it is what upper
  // views layer does - when Widget initialize DesktopWindowTreeHost, the Show()
  // is called later down the road, but Maximize may be called earlier. We
  // cannot process them and set a pending state instead, because ShellSurface
  // is not created by that moment.
  auto window =
      delegate.CreateWaylandWindow(connection_.get(), std::move(properties));

  WaylandTestBase::SyncDisplay();

  // Make sure the window is initialized to normal state from the beginning.
  EXPECT_EQ(PlatformWindowState::kNormal, window->GetPlatformWindowState());

  // The state must not be changed to the fullscreen before the surface is
  // activated.
  const uint32_t surface_id = window->root_surface()->get_surface_id();
  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface =
        server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_FALSE(mock_surface->xdg_surface());
  });

  if (async_state) {
    EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
    window->SetFullscreen(true, display::kInvalidDisplayId);
    EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kNormal);
  } else {
    // We must receive a state change after SetFullscreen.
    EXPECT_CALL(delegate,
                OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
                                     Eq(PlatformWindowState::kFullScreen)))
        .Times(1);
    window->SetFullscreen(true, display::kInvalidDisplayId);
    // The state of the window must already be fullscreen one.
    EXPECT_EQ(window->GetPlatformWindowState(),
              PlatformWindowState::kFullScreen);
  }

  WaylandTestBase::SyncDisplay();
  Mock::VerifyAndClearExpectations(&delegate);

  // Show and Activate the surface.
  window->Show(false);

  if (async_state) {
    EXPECT_CALL(delegate,
                OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
                                     Eq(PlatformWindowState::kFullScreen)))
        .Times(1);
    wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
    states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
    SendConfigureEvent(surface_id, {800, 600}, states);
    AdvanceFrameToCurrent(window.get(), delegate);
  } else {
    // We mustn't receive any state changes if that does not differ from the
    // last state.
    EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
    wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
    states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
    SendConfigureEvent(surface_id, {0, 0}, states);
  }

  EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
  Mock::VerifyAndClearExpectations(&delegate);
}

TEST_P(WaylandWindowTest, StartWithFullscreen) {
  DoStartWithFullscreenTest(/*async_state=*/false);
}

TEST_P(WaylandWindowTest, StartWithFullscreenAsync) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kAsyncFullscreenWindowState);
  DoStartWithFullscreenTest(/*async_state=*/true);
}

void WaylandWindowTest::DoStartMaximizedTest(bool async_state) {
  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(100, 100);
  properties.type = PlatformWindowType::kWindow;
  // We need to create a window avoid calling Show() on it as it is what upper
  // views layer does - when Widget initialize DesktopWindowTreeHost, the Show()
  // is called later down the road, but Maximize may be called earlier. We
  // cannot process them and set a pending state instead, because ShellSurface
  // is not created by that moment.
  auto window =
      delegate.CreateWaylandWindow(connection_.get(), std::move(properties));

  WaylandTestBase::SyncDisplay();

  // Make sure the window is initialized to normal state from the beginning.
  EXPECT_EQ(PlatformWindowState::kNormal, window->GetPlatformWindowState());

  // The state gets changed to maximize and the delegate notified.
  const uint32_t surface_id = window->root_surface()->get_surface_id();
  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface =
        server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_FALSE(mock_surface->xdg_surface());
  });

  if (async_state) {
    EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
    window->Maximize();
    EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kNormal);
  } else {
    // We must receive a state change after Show is called.
    EXPECT_CALL(delegate,
                OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
                                     Eq(PlatformWindowState::kMaximized)))
        .Times(1);
    window->Maximize();
    // The state of the window must already be fullscreen one.
    EXPECT_EQ(window->GetPlatformWindowState(),
              PlatformWindowState::kMaximized);
  }

  WaylandTestBase::SyncDisplay();
  Mock::VerifyAndClearExpectations(&delegate);

  // Show the window now.
  window->Show(false);

  if (async_state) {
    EXPECT_CALL(delegate,
                OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
                                     Eq(PlatformWindowState::kMaximized)))
        .Times(1);

    wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
    states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
    SendConfigureEvent(surface_id, {800, 600}, states);
    AdvanceFrameToCurrent(window.get(), delegate);
  } else {
    // Window show state should be already up to date, so delegate is not
    // notified.
    EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
    EXPECT_EQ(window->GetPlatformWindowState(),
              PlatformWindowState::kMaximized);
    // Activate the surface.
    wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
    states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
    SendConfigureEvent(surface_id, {0, 0}, states);
  }

  EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kMaximized);
  Mock::VerifyAndClearExpectations(&delegate);
}

TEST_P(WaylandWindowTest, StartMaximized) {
  DoStartMaximizedTest(/*async_state=*/false);
}

TEST_P(WaylandWindowTest, StartMaximizedAsync) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kAsyncFullscreenWindowState);
  DoStartMaximizedTest(/*async_state=*/true);
}

TEST_P(WaylandWindowTest, CompositorSideStateChanges) {
  // Real insets used by default on HiDPI.
  const auto kInsets = gfx::Insets::TLBR(38, 44, 55, 44);
  const auto kNormalBounds = window_->GetBoundsInDIP();

  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);

  // Set nonzero insets and ensure that they are only used when the window has
  // normal state.
  // See https://crbug.com/1274629
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
      .WillRepeatedly(Return(gfx::Insets()));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
      .Times(1);
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect{2000, 2000}));
  });

  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
  SendConfigureEvent(surface_id_, {2000, 2000}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMaximized);
  VerifyAndClearExpectations();

  // Unmaximize
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
      .WillRepeatedly(Return(kInsets));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
      .Times(1);
  PostToServerAndWait(
      [id = surface_id_, insets = kInsets,
       bounds = kNormalBounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface,
                    SetWindowGeometry(gfx::Rect(
                        insets.left(), insets.top(),
                        bounds.width() - (insets.left() + insets.right()),
                        bounds.height() - (insets.top() + insets.bottom()))));
      });
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Now, set to fullscreen.
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kFullScreen))
      .WillRepeatedly(Return(gfx::Insets()));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
      .Times(1);
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(2005, 2005)));
  });
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
  SendConfigureEvent(surface_id_, {2005, 2005}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Unfullscreen
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
      .WillRepeatedly(Return(kInsets));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
      .Times(1);
  PostToServerAndWait(
      [id = surface_id_, insets = kInsets,
       bounds = kNormalBounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface,
                    SetWindowGeometry(gfx::Rect(
                        insets.left(), insets.top(),
                        bounds.width() - (insets.left() + insets.right()),
                        bounds.height() - (insets.top() + insets.bottom()))));
      });
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Now, maximize, fullscreen and restore.
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
      .WillRepeatedly(Return(gfx::Insets()));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
      .Times(1);
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(2000, 2000)));
  });
  states = InitializeWlArrayWithActivatedState();
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
  SendConfigureEvent(surface_id_, {2000, 2000}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kFullScreen))
      .WillRepeatedly(Return(gfx::Insets()));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
      .Times(1);
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(2005, 2005)));
  });
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
  SendConfigureEvent(surface_id_, {2005, 2005}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Restore
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
      .WillRepeatedly(Return(kInsets));
  EXPECT_CALL(delegate_,
              OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
      .Times(1);
  PostToServerAndWait(
      [id = surface_id_, insets = kInsets,
       bounds = kNormalBounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface,
                    SetWindowGeometry(gfx::Rect(
                        insets.left(), insets.top(),
                        bounds.width() - (insets.left() + insets.right()),
                        bounds.height() - (insets.top() + insets.bottom()))));
      });
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();
}

void WaylandWindowTest::DoSetMaximizedFullscreenAndRestoreTest(
    bool async_state) {
  constexpr gfx::Rect kNormalBounds{500, 300};
  constexpr gfx::Rect kMaximizedBounds{800, 600};

  // Make sure the window has normal state initially.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Deactivate the surface.
  wl::ScopedWlArray empty_state({});
  SendConfigureEvent(surface_id_, {0, 0}, empty_state);

  auto active_maximized = MakeStateArray(
      {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});

  PostToServerAndWait([id = surface_id_, bounds = kMaximizedBounds](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
  });
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  window_->Maximize();
  if (async_state) {
    // State changes are asynchronous.
    EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  } else {
    // State changes are synchronous.
    EXPECT_EQ(PlatformWindowState::kMaximized,
              window_->GetPlatformWindowState());
  }
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetFullscreen());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  window_->SetFullscreen(true, display::kInvalidDisplayId);
  if (async_state) {
    // State changes are asynchronous.
    EXPECT_EQ(PlatformWindowState::kMaximized,
              window_->GetPlatformWindowState());
  } else {
    // State changes are synchronous.
    EXPECT_EQ(PlatformWindowState::kFullScreen,
              window_->GetPlatformWindowState());
  }
  auto active_fullscreen = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED,
                                           XDG_TOPLEVEL_STATE_MAXIMIZED,
                                           XDG_TOPLEVEL_STATE_FULLSCREEN});
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_fullscreen);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  EXPECT_EQ(PlatformWindowState::kFullScreen,
            window_->GetPlatformWindowState());
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetFullscreen());
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  window_->Restore();
  if (async_state) {
    EXPECT_EQ(PlatformWindowState::kFullScreen,
              window_->GetPlatformWindowState());
  } else {
    EXPECT_EQ(PlatformWindowState::kMaximized,
              window_->GetPlatformWindowState());
  }
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  // Verify that the state has not been changed.
  EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
  });
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  window_->Restore();
  if (async_state) {
    EXPECT_EQ(PlatformWindowState::kMaximized,
              window_->GetPlatformWindowState());
  } else {
    EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
  }
  // Reinitialize wl_array, which removes previous old states.
  auto active = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  // Verify that the state has not been changed.
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
}

TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestore) {
  DoSetMaximizedFullscreenAndRestoreTest(/*async_state=*/false);
}

TEST_P(WaylandWindowTest, SetMaximizedFullscreenAndRestoreAsync) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kAsyncFullscreenWindowState);
  DoSetMaximizedFullscreenAndRestoreTest(/*async_state=*/true);
}

TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximize) {
  const gfx::Rect current_bounds = window_->GetBoundsInDIP();

  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();

  gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_TRUE(restored_bounds.IsEmpty());
  gfx::Rect bounds = window_->GetBoundsInDIP();

  constexpr gfx::Rect kMaximizedBounds(1024, 768);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->Maximize();
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(bounds, restored_bounds);

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  // Both in XdgV5 and XdgV6, surfaces implement SetWindowGeometry method.
  // Thus, using a toplevel object in XdgV6 case is not right thing. Use a
  // surface here instead.
  PostToServerAndWait(
      [id = surface_id_, current_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface,
                    SetWindowGeometry(gfx::Rect{current_bounds.size()}));
      });
  window_->Restore();
  // Reinitialize wl_array, which removes previous old states.
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  bounds = window_->GetBoundsInDIP();
  EXPECT_EQ(bounds, restored_bounds);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, gfx::Rect());
}

TEST_P(WaylandWindowTest, RestoreBoundsAfterFullscreen) {
  const gfx::Rect current_bounds = window_->GetBoundsInDIP();

  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, gfx::Rect());
  gfx::Rect bounds = window_->GetBoundsInDIP();

  constexpr gfx::Rect kFullscreenBounds(1280, 720);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->SetFullscreen(true, display::kInvalidDisplayId);
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
  SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(bounds, restored_bounds);

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  // Both in XdgV5 and XdgV6, surfaces implement SetWindowGeometry method.
  // Thus, using a toplevel object in XdgV6 case is not right thing. Use a
  // surface here instead.
  PostToServerAndWait(
      [id = surface_id_, current_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface,
                    SetWindowGeometry(gfx::Rect(current_bounds.size())));
      });
  window_->Restore();
  // Reinitialize wl_array, which removes previous old states.
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  bounds = window_->GetBoundsInDIP();
  EXPECT_EQ(bounds, restored_bounds);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, gfx::Rect());
}

TEST_P(WaylandWindowTest, RestoreBoundsAfterMaximizeAndFullscreen) {
  const gfx::Rect current_bounds = window_->GetBoundsInDIP();

  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();

  gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, gfx::Rect());
  gfx::Rect bounds = window_->GetBoundsInDIP();

  constexpr gfx::Rect kMaximizedBounds(1024, 768);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->Maximize();
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(bounds, restored_bounds);

  constexpr gfx::Rect kFullscreenBounds(1280, 720);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->SetFullscreen(true, display::kInvalidDisplayId);
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
  SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  gfx::Rect fullscreen_restore_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, fullscreen_restore_bounds);

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->Maximize();
  // Reinitialize wl_array, which removes previous old states.
  states = InitializeWlArrayWithActivatedState();
  states.AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED);
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, fullscreen_restore_bounds);

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  // Both in XdgV5 and XdgV6, surfaces implement SetWindowGeometry method.
  // Thus, using a toplevel object in XdgV6 case is not right thing. Use a
  // surface here instead.
  PostToServerAndWait(
      [id = surface_id_, current_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface,
                    SetWindowGeometry(gfx::Rect(current_bounds.size())));
      });
  window_->Restore();
  // Reinitialize wl_array, which removes previous old states.
  states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  bounds = window_->GetBoundsInDIP();
  EXPECT_EQ(bounds, restored_bounds);
  restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, gfx::Rect());
}

TEST_P(WaylandWindowTest, SendsBoundsOnRequest) {
  const gfx::Rect initial_bounds = window_->GetBoundsInDIP();

  const gfx::Rect new_bounds =
      gfx::Rect(initial_bounds.width() + 10, initial_bounds.height() + 10);
  EXPECT_CALL(delegate_, OnBoundsChanged(kDefaultBoundsChange));

  PostToServerAndWait([id = surface_id_,
                       new_bounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
        .Times(1);
  });
  window_->SetBoundsInDIP(new_bounds);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Restored bounds should keep empty value.
  gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
  EXPECT_EQ(restored_bounds, gfx::Rect());
}

TEST_P(WaylandWindowTest, UpdateWindowRegion) {
  // Change bounds.
  const gfx::Rect initial_bounds = window_->GetBoundsInDIP();
  const gfx::Rect new_bounds =
      gfx::Rect(initial_bounds.width() + 10, initial_bounds.height() + 10);
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
  });
  window_->SetBoundsInDIP(new_bounds);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait(
      [id = surface_id_, new_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
        EXPECT_EQ(mock_surface->input_region(), new_bounds);
      });

  // Maximize.
  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
  });
  constexpr gfx::Rect kMaximizedBounds(1024, 768);
  window_->Maximize();
  AddStateToWlArray(XDG_TOPLEVEL_STATE_MAXIMIZED, states.get());
  SendConfigureEvent(surface_id_, kMaximizedBounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_,
                       kMaximizedBounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    EXPECT_EQ(mock_surface->opaque_region(), kMaximizedBounds);
    EXPECT_EQ(mock_surface->input_region(), kMaximizedBounds);
  });

  // Restore.
  const gfx::Rect restored_bounds = window_->GetRestoredBoundsInDIP();
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(1);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(1);
  });
  window_->Restore();
  // Reinitialize wl_array, which removes previous old states.
  auto active = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, {0, 0}, active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait(
      [id = surface_id_, restored_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        EXPECT_EQ(mock_surface->opaque_region(), restored_bounds);
        EXPECT_EQ(mock_surface->input_region(), restored_bounds);
      });
  AdvanceFrameToCurrent(window_.get(), delegate_);
}

TEST_P(WaylandWindowTest, CanDispatchMouseEventFocus) {
  // SetPointerFocusedWindow requires a WaylandPointer.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  SetPointerFocusedWindow(window_.get());
  Event::DispatcherApi(&test_mouse_event_).set_target(window_.get());
  EXPECT_TRUE(window_->CanDispatchEvent(&test_mouse_event_));
}

TEST_P(WaylandWindowTest, SetCursorUsesCursorShapeForCommonTypes) {
  SetPointerFocusedWindow(window_.get());
  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();

  // Verify some commonly-used cursors.
  EXPECT_CALL(*mock_cursor_shape,
              SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT));
  auto pointer_cursor = AsPlatformCursor(
      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kPointer));
  window_->SetCursor(pointer_cursor.get());
  VerifyAndClearExpectations();

  EXPECT_CALL(*mock_cursor_shape,
              SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER));
  auto hand_cursor = AsPlatformCursor(
      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kHand));
  window_->SetCursor(hand_cursor.get());
  VerifyAndClearExpectations();

  EXPECT_CALL(*mock_cursor_shape,
              SetCursorShape(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT));
  auto ibeam_cursor = AsPlatformCursor(
      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kIBeam));
  window_->SetCursor(ibeam_cursor.get());
  VerifyAndClearExpectations();
}

TEST_P(WaylandWindowTest, SetCursorCallsCursorShapeOncePerCursor) {
  SetPointerFocusedWindow(window_.get());
  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
  auto hand_cursor = AsPlatformCursor(
      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kHand));
  // Setting the same cursor twice on the client only calls the server once.
  EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(1);
  window_->SetCursor(hand_cursor.get());
  window_->SetCursor(hand_cursor.get());
}

TEST_P(WaylandWindowTest, SetCursorDoesNotUseCursorShapeForNoneCursor) {
  SetPointerFocusedWindow(window_.get());
  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();
  EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(0);
  auto none_cursor = AsPlatformCursor(
      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kNone));
  window_->SetCursor(none_cursor.get());
}

TEST_P(WaylandWindowTest, SetCursorDoesNotUseCursorShapeForCustomCursors) {
  SetPointerFocusedWindow(window_.get());
  MockCursorShape* mock_cursor_shape = InstallMockCursorShape();

  // Custom cursors require bitmaps, so they do not use server-side cursors.
  EXPECT_CALL(*mock_cursor_shape, SetCursorShape(_)).Times(0);
  auto custom_cursor = AsPlatformCursor(
      base::MakeRefCounted<BitmapCursor>(mojom::CursorType::kCustom, SkBitmap(),
                                         gfx::Point(), kDefaultCursorScale));
  window_->SetCursor(custom_cursor.get());
}

ACTION_P(CloneEvent, ptr) {
  *ptr = arg0->Clone();
}

TEST_P(WaylandWindowTest, DispatchEvent) {
  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));
  window_->DispatchEvent(&test_mouse_event_);
  ASSERT_TRUE(event);
  ASSERT_TRUE(event->IsMouseEvent());
  auto* mouse_event = event->AsMouseEvent();
  EXPECT_EQ(mouse_event->location_f(), test_mouse_event_.location_f());
  EXPECT_EQ(mouse_event->root_location_f(),
            test_mouse_event_.root_location_f());
  EXPECT_EQ(mouse_event->time_stamp(), test_mouse_event_.time_stamp());
  EXPECT_EQ(mouse_event->button_flags(), test_mouse_event_.button_flags());
  EXPECT_EQ(mouse_event->changed_button_flags(),
            test_mouse_event_.changed_button_flags());
}

TEST_P(WaylandWindowTest, DispatchEventResult) {
  // Create an arbitrary wayland window with a test delegate.
  TestWaylandWindowDelegate window_delegate;
  PlatformWindowInitProperties properties(gfx::Rect(10, 10));
  auto window = WaylandWindow::Create(&window_delegate, connection_.get(),
                                      std::move(properties));

  KeyEvent event_1(EventType::kKeyPressed, VKEY_0, 0);
  window_delegate.SetDispatchEventCallback(
      base::BindOnce([](Event* event) { event->SetSkipped(); }));
  EXPECT_EQ(window->DispatchEvent(&event_1), POST_DISPATCH_PERFORM_DEFAULT);

  KeyEvent event_2(EventType::kKeyPressed, VKEY_0, 0);
  window_delegate.SetDispatchEventCallback(
      base::BindOnce([](Event* event) { event->StopPropagation(); }));
  EXPECT_EQ(window->DispatchEvent(&event_2), POST_DISPATCH_STOP_PROPAGATION);

  KeyEvent event_3(EventType::kKeyPressed, VKEY_0, 0);
  window_delegate.SetDispatchEventCallback(
      base::BindOnce([](Event* event) { event->SetHandled(); }));
  EXPECT_EQ(window->DispatchEvent(&event_3), POST_DISPATCH_STOP_PROPAGATION);

  KeyEvent event_4(EventType::kKeyPressed, VKEY_0, 0);
  window_delegate.SetDispatchEventCallback(base::BindOnce([](Event* event) {
    // Do nothing.
  }));
  EXPECT_EQ(window->DispatchEvent(&event_4), POST_DISPATCH_NONE);
}

TEST_P(WaylandWindowTest, ConfigureEvent) {
  wl::ScopedWlArray states({});

  // The surface must react on each configure event and send bounds to its
  // delegate.
  constexpr gfx::Size kSize{1000, 1000};
  uint32_t serial = 12u;

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  // Responding to a configure event, the window geometry in here must respect
  // the sizing negotiations specified by the configure event.
  // |xdg_surface| must receive the following calls in both xdg_shell_v5 and
  // xdg_shell_v6. Other calls like SetTitle or SetMaximized are received by
  // xdg_toplevel in xdg_shell_v6 and by xdg_surface in xdg_shell_v5.
  PostToServerAndWait(
      [id = surface_id_, kSize, serial](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        auto* xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(kSize))).Times(1);
        EXPECT_CALL(*xdg_surface, AckConfigure(serial));
      });
  SendConfigureEvent(surface_id_, kSize, states, serial);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  constexpr gfx::Size kNewSize{1500, 1000};

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  PostToServerAndWait([id = surface_id_, kNewSize,
                       serial](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(kNewSize))).Times(1);
    EXPECT_CALL(*xdg_surface, AckConfigure(serial + 1));
  });
  SendConfigureEvent(surface_id_, kNewSize, states, ++serial);
  AdvanceFrameToCurrent(window_.get(), delegate_);
}

TEST_P(WaylandWindowTest, ConfigureEventWithNulledSize) {
  wl::ScopedWlArray states({});

  // |xdg_surface| must receive the following calls in both xdg_shell_v5 and
  // xdg_shell_v6. Other calls like SetTitle or SetMaximized are received by
  // xdg_toplevel in xdg_shell_v6 and by xdg_surface in xdg_shell_v5.
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, AckConfigure(14u));
  });

  // If Wayland sends configure event with 0 width and 0 size, client should
  // call back with desired sizes. In this case, that's the actual size of
  // the window.
  SendConfigureEvent(surface_id_, {0, 0}, states, 14u);
}

TEST_P(WaylandWindowTest, ConfigureEventIsNotAckedMultipleTimes) {
  constexpr gfx::Rect kNormalBounds{500, 300};
  constexpr gfx::Rect kSecondBounds{600, 600};

  // Configure event makes Wayland update bounds, but does not change toplevel
  // input region, opaque region or window geometry immediately. Such actions
  // are postponed to OnSequencePoint();
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(0);
    EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetInputRegion(_)).Times(0);
  });

  auto state = InitializeWlArrayWithActivatedState();
  constexpr uint32_t kConfigureSerial = 2u;
  SendConfigureEvent(surface_id_, kNormalBounds.size(), state,
                     kConfigureSerial);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kNormalBounds](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
    EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial));
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
    EXPECT_CALL(*mock_surface, SetInputRegion(_));
  });

  // Get the viz sequence ID which will let us ack the configure event.
  auto viz_seq = delegate_.viz_seq();

  // Insert another change which will increase the viz sequence ID here.
  // We want to make sure that when this viz sequence ID is reached, we
  // don't send another ack for the configure.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
  window_->SetBoundsInDIP(kSecondBounds);

  AdvanceFrameToGivenVizSequenceId(window_.get(), delegate_, viz_seq);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_, bounds = kSecondBounds](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
    EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
    EXPECT_CALL(*mock_surface, SetOpaqueRegion(_));
    EXPECT_CALL(*mock_surface, SetInputRegion(_));
  });
  AdvanceFrameToCurrent(window_.get(), delegate_);
}

TEST_P(WaylandWindowTest, ManyConfigureEventsDoesNotCrash) {
  constexpr uint32_t kConfigureSerial = 2u;

  auto state = InitializeWlArrayWithActivatedState();
  gfx::Size size{500, 300};
  for (int i = 0; i < 3000; ++i) {
    SendConfigureEvent(surface_id_, size, state, kConfigureSerial + i);
    size.Enlarge(1, 1);
  }
  AdvanceFrameToCurrent(window_.get(), delegate_);
}

// If the server immediately changes the bounds after a window is initialised,
// make sure that the client doesn't wait for a new frame to be produced.
// See https://crbug.com/1427954.
TEST_P(WaylandWindowTest, InitialConfigureFollowedByBoundsChangeCompletesAck) {
  constexpr gfx::Rect kFirstBounds{0, 0, 800, 600};
  constexpr gfx::Rect kSecondBounds{50, 50, 800, 600};
  constexpr uint32_t kConfigureSerial = 2u;

  // Make sure that we start off with the initial bounds we expect.
  EXPECT_EQ(kFirstBounds, window_->latched_state().bounds_dip);
  EXPECT_EQ(kFirstBounds, window_->applied_state().bounds_dip);

  // Expect an origin change.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange{true})));

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial)).Times(1);
  });

  {
    WaylandWindow::WindowStates window_states;
    window_states.is_activated = true;
    window_->HandleToplevelConfigureWithOrigin(
        kSecondBounds.x(), kSecondBounds.y(), kSecondBounds.width(),
        kSecondBounds.height(), window_states);
  }
  window_->HandleSurfaceConfigure(kConfigureSerial);

  // Don't call AdvanceFrameToCurrent here, because just updating the
  // bounds origin won't generate a new frame. The client should ack
  // even if there is no new frame produced.
}

TEST_P(WaylandWindowTest, OnActivationChanged) {
  uint32_t serial = 0;

  MockWaylandPlatformWindowDelegate new_window_delegate(connection_.get());
  auto new_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kWindow, gfx::Rect(100, 100), &new_window_delegate);
  ASSERT_TRUE(new_window);
  auto new_window_surface_id = new_window->root_surface()->get_surface_id();

  MockWaylandPlatformWindowDelegate menu_delegate(connection_.get());
  auto menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                            gfx::Rect(100, 100), &menu_delegate,
                                            new_window->GetWidget());
  ASSERT_TRUE(menu);

  wl::ScopedWlArray empty_state({});
  SendConfigureEvent(surface_id_, {0, 0}, empty_state, ++serial);
  SendConfigureEvent(new_window_surface_id, {0, 0}, empty_state, ++serial);

  // Request active decorations.
  wl::ScopedWlArray active_state = InitializeWlArrayWithActivatedState();
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  SendConfigureEvent(surface_id_, {0, 0}, active_state, ++serial);
  VerifyAndClearExpectations();

  // Plug keyboard.
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_KEYBOARD);
  });
  VerifyAndClearExpectations();

  // Focus keyboard.
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  SetKeyboardFocusedWindow(window_.get());
  VerifyAndClearExpectations();

  // Switch keyboard focus to other window.
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
  EXPECT_CALL(new_window_delegate, OnActivationChanged(Eq(true)));
  SetKeyboardFocusedWindow(new_window.get());
  VerifyAndClearExpectations();

  // Switch keyboard focus to menu in other window.
  EXPECT_CALL(new_window_delegate, OnActivationChanged(_)).Times(0);
  SetKeyboardFocusedWindow(menu.get());
  VerifyAndClearExpectations();

  // Unfocus keyboard.
  EXPECT_CALL(new_window_delegate, OnActivationChanged(Eq(false)));
  SetKeyboardFocusedWindow(nullptr);
  VerifyAndClearExpectations();

  // Unplug keyboard - should fall back to xdg-shell activated state.
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(), 0);
  });
  VerifyAndClearExpectations();

  // Request inactive decorations.
  EXPECT_CALL(delegate_, OnActivationChanged(false));
  SendConfigureEvent(surface_id_, {0, 0}, empty_state, ++serial);
}

TEST_P(WaylandWindowTest, OnPaintAsActiveChanged) {
  uint32_t serial = 0;
  wl::ScopedWlArray empty_state({});
  wl::ScopedWlArray active_state = InitializeWlArrayWithActivatedState();

  // SetUp has already activated the surface; redundant activated
  // configure is not a transition and must not fire.
  EXPECT_CALL(delegate_, OnPaintAsActiveChanged(_)).Times(0);
  SendConfigureEvent(surface_id_, {0, 0}, active_state, ++serial);
  VerifyAndClearExpectations();

  // Compositor clears xdg_activated: paint-as-active fires false.
  EXPECT_CALL(delegate_, OnPaintAsActiveChanged(Eq(false)));
  SendConfigureEvent(surface_id_, {0, 0}, empty_state, ++serial);
  VerifyAndClearExpectations();

  // Redundant inactive configure: no fire.
  EXPECT_CALL(delegate_, OnPaintAsActiveChanged(_)).Times(0);
  SendConfigureEvent(surface_id_, {0, 0}, empty_state, ++serial);
  VerifyAndClearExpectations();

  // Compositor re-marks activated: paint-as-active fires true.
  EXPECT_CALL(delegate_, OnPaintAsActiveChanged(Eq(true)));
  SendConfigureEvent(surface_id_, {0, 0}, active_state, ++serial);
  VerifyAndClearExpectations();

  // Plug a keyboard and toggle focus. xdg_activated is unchanged, so
  // paint-as-active must not fire even though OnActivationChanged does.
  // This covers the interactive move/resize scenario where the compositor
  // temporarily revokes device focus but keeps xdg_activated set.
  EXPECT_CALL(delegate_, OnPaintAsActiveChanged(_)).Times(0);
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_KEYBOARD);
  });
  SetKeyboardFocusedWindow(window_.get());
  SetKeyboardFocusedWindow(nullptr);
  VerifyAndClearExpectations();
}

TEST_P(WaylandWindowTest, OnAcceleratedWidgetDestroy) {
  window_.reset();
}

TEST_P(WaylandWindowTest, CanCreateMenuWindow) {
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());

  // SetPointerFocus(true) requires a WaylandPointer.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(
        server->seat()->resource(),
        WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  ASSERT_TRUE(connection_->seat()->touch());
  SetPointerFocusedWindow(window_.get());

  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate);
  EXPECT_TRUE(menu_window);

  SetPointerFocusedWindow(window_.get());
  window_->set_touch_focus(false);

  // Given that there is no parent passed and we don't have any focused windows,
  // Wayland must still create a window.
  menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate);
  EXPECT_TRUE(menu_window);

  window_->set_touch_focus(true);

  menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate);
  EXPECT_TRUE(menu_window);
}

TEST_P(WaylandWindowTest, CreateAndDestroyNestedMenuWindow) {
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  gfx::AcceleratedWidget menu_window_widget;
  EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_))
      .WillOnce(SaveArg<0>(&menu_window_widget));

  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate,
      widget_);
  EXPECT_TRUE(menu_window);
  ASSERT_NE(menu_window_widget, gfx::kNullAcceleratedWidget);

  MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
      connection_.get());
  std::unique_ptr<WaylandWindow> nested_menu_window =
      CreateWaylandWindowWithParams(
          PlatformWindowType::kMenu, gfx::Rect(20, 0, 10, 10),
          &nested_menu_window_delegate, menu_window_widget);
  EXPECT_TRUE(nested_menu_window);
}

TEST_P(WaylandWindowTest, DispatchesLocatedEventsToCapturedWindow) {
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10, 10, 10),
      &menu_window_delegate, widget_);
  EXPECT_TRUE(menu_window);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  SetPointerFocusedWindow(window_.get());

  // Make sure the events are handled by the window that has the pointer focus.
  VerifyCanDispatchMouseEvents(window_.get(), {menu_window.get()});

  // The |window_| that has the pointer focus must receive the event.
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // The event is sent in local surface coordinates of the |window|.
    wl_pointer_send_motion(server->seat()->pointer()->resource(),
                           server->GetNextSerial(), wl_fixed_from_double(10.75),
                           wl_fixed_from_double(20.375));
    wl_pointer_send_frame(server->seat()->pointer()->resource());
  });

  ASSERT_TRUE(event->IsLocatedEvent());
  EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 20));

  // Set capture to menu window now.
  menu_window->SetCapture();

  // It's still the |window_| that can dispatch the events, but it will reroute
  // the event to correct window and fix the location.
  VerifyCanDispatchMouseEvents(window_.get(), {menu_window.get()});

  // The |window_| that has the pointer focus must receive the event.
  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
  std::unique_ptr<Event> event2;
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
      .WillOnce(CloneEvent(&event2));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // The event is sent in local surface coordinates of the |window|.
    wl_pointer_send_motion(server->seat()->pointer()->resource(),
                           server->GetNextSerial(), wl_fixed_from_double(10.75),
                           wl_fixed_from_double(20.375));
    wl_pointer_send_frame(server->seat()->pointer()->resource());
  });

  ASSERT_TRUE(event2->IsLocatedEvent());
  EXPECT_EQ(event2->AsLocatedEvent()->location(), gfx::Point(0, 10));

  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
  std::unique_ptr<Event> event3;
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
      .WillOnce(CloneEvent(&event3));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // The event is sent in local surface coordinates of the |window|.
    wl_pointer_send_motion(server->seat()->pointer()->resource(),
                           server->GetNextSerial(), wl_fixed_from_double(2.75),
                           wl_fixed_from_double(8.375));
    wl_pointer_send_frame(server->seat()->pointer()->resource());
  });

  ASSERT_TRUE(event3->IsLocatedEvent());
  EXPECT_EQ(event3->AsLocatedEvent()->location(), gfx::Point(-8, -2));

  // If nested menu window is added, the events are still correctly translated
  // to the captured window.
  MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
      connection_.get());
  std::unique_ptr<WaylandWindow> nested_menu_window =
      CreateWaylandWindowWithParams(
          PlatformWindowType::kMenu, gfx::Rect(15, 18, 10, 10),
          &nested_menu_window_delegate, menu_window->GetWidget());
  EXPECT_TRUE(nested_menu_window);

  SetPointerFocusedWindow(nested_menu_window.get());

  // The event is processed by the window that has the pointer focus, but
  // dispatched by the window that has the capture.
  VerifyCanDispatchMouseEvents(nested_menu_window.get(),
                               {window_.get(), menu_window.get()});
  EXPECT_TRUE(menu_window->HasCapture());

  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
  EXPECT_CALL(nested_menu_window_delegate, DispatchEvent(_)).Times(0);
  std::unique_ptr<Event> event4;
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
      .WillOnce(CloneEvent(&event4));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // The event is sent in local surface coordinates of the
    // |nested_menu_window|.
    wl_pointer_send_motion(server->seat()->pointer()->resource(),
                           server->GetNextSerial(), wl_fixed_from_double(2.75),
                           wl_fixed_from_double(8.375));
    wl_pointer_send_frame(server->seat()->pointer()->resource());
  });

  ASSERT_TRUE(event4->IsLocatedEvent());
  EXPECT_EQ(event4->AsLocatedEvent()->location(), gfx::Point(7, 16));

  nested_menu_window.reset();
  menu_window.reset();
}

// Verify that located events are translated correctly when the windows have
// geometry with non-zero offset.
// See https://crbug.com/1292486.
TEST_P(WaylandWindowTest, ConvertEventToTarget) {
  constexpr gfx::Rect kMainWindowBounds{956, 556};
  const auto kMainWindowInsets = gfx::Insets::TLBR(24, 28, 32, 28);

  auto bounds_with_insets = kMainWindowBounds;
  bounds_with_insets.Inset(kMainWindowInsets);
  EXPECT_CALL(delegate_, OnBoundsChanged(_));
  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
      .WillRepeatedly(Return(kMainWindowInsets));
  PostToServerAndWait([id = surface_id_, bounds_with_insets](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds_with_insets));
  });
  window_->SetBoundsInDIP(kMainWindowBounds);
  SendConfigureEvent(surface_id_, bounds_with_insets.size(),
                     MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED}));
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Create a menu.
  constexpr gfx::Rect kMenuBounds{100, 100, 80, 50};
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, kMenuBounds, &menu_window_delegate, widget_);
  EXPECT_TRUE(menu_window);

  // Now translate the event located at (0, 0) in the parent window into the
  // coordinate system of the menu.  Its coordinates must be equal to:
  //     -(offset of the menu).
  constexpr gfx::PointF kParentPoint{0, 0};
  ui::MouseEvent event(ui::EventType::kMouseMoved, kParentPoint, kParentPoint,
                       {}, ui::EF_NONE, ui::EF_NONE);

  ui::Event::DispatcherApi dispatcher_api(&event);
  dispatcher_api.set_target(window_.get());

  ui::WaylandEventSource::ConvertEventToTarget(menu_window.get(), &event);
  EXPECT_EQ(event.AsLocatedEvent()->x(), -kMenuBounds.x());
  EXPECT_EQ(event.AsLocatedEvent()->y(), -kMenuBounds.y());
}

// Tests that the event grabber gets the events processed by its toplevel parent
// window iff they belong to the same "family". Otherwise, events mustn't be
// rerouted from another toplevel window to the event grabber.
TEST_P(WaylandWindowTest,
       DispatchesLocatedEventsToCapturedWindowInTheSameStack) {
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(30, 40, 20, 50),
      &menu_window_delegate, widget_);
  EXPECT_TRUE(menu_window);

  // Second toplevel window has the same bounds as the |window_|.
  MockWaylandPlatformWindowDelegate toplevel_window2_delegate(
      connection_.get());
  std::unique_ptr<WaylandWindow> toplevel_window2 =
      CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
                                    window_->GetBoundsInDIP(),
                                    &toplevel_window2_delegate);
  EXPECT_TRUE(toplevel_window2);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  SetPointerFocusedWindow(window_.get());

  // Make sure the events are handled by the window that has the pointer focus.
  VerifyCanDispatchMouseEvents(window_.get(),
                               {menu_window.get(), toplevel_window2.get()});

  menu_window->SetCapture();

  // The |menu_window| that has capture must receive the event.
  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
  EXPECT_CALL(toplevel_window2_delegate, DispatchEvent(_)).Times(0);
  std::unique_ptr<Event> event;
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_))
      .WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // The event is sent in local surface coordinates of the |window|.
    wl_pointer_send_motion(server->seat()->pointer()->resource(),
                           server->GetNextSerial(), wl_fixed_from_double(10.75),
                           wl_fixed_from_double(20.375));
    wl_pointer_send_frame(server->seat()->pointer()->resource());
  });

  ASSERT_TRUE(event->IsLocatedEvent());
  EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(-20, -20));

  // Now, pretend that the second toplevel window gets the pointer focus - the
  // event grabber must be disragerder now.
  SetPointerFocusedWindow(toplevel_window2.get());

  VerifyCanDispatchMouseEvents(toplevel_window2.get(),
                               {menu_window.get(), window_.get()});

  // The |toplevel_window2| that has capture and must receive the event.
  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(0);
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
  event.reset();
  EXPECT_CALL(toplevel_window2_delegate, DispatchEvent(_))
      .WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // The event is sent in local surface coordinates of the |toplevel_window2|
    // (they're basically the same as the |window| has.)
    wl_pointer_send_motion(server->seat()->pointer()->resource(),
                           server->GetNextSerial(), wl_fixed_from_double(10.75),
                           wl_fixed_from_double(20.375));
    wl_pointer_send_frame(server->seat()->pointer()->resource());
  });

  ASSERT_TRUE(event->IsLocatedEvent());
  EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 20));
}

TEST_P(WaylandWindowTest, DispatchesKeyboardEventToToplevelWindow) {
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10, 10, 10),
      &menu_window_delegate, widget_);
  EXPECT_TRUE(menu_window);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_KEYBOARD);
  });
  ASSERT_TRUE(connection_->seat()->keyboard());
  SetKeyboardFocusedWindow(menu_window.get());

  // Disable auto-repeat so that it doesn't ruin our expectations.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_keyboard_send_repeat_info(server->seat()->keyboard()->resource(), 0, 0);
  });

  // Even though the menu window has the keyboard focus, the keyboard events are
  // dispatched by the root parent wayland window in the end.
  VerifyCanDispatchKeyEvents({menu_window.get()}, {window_.get()});
  EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_keyboard_send_key(server->seat()->keyboard()->resource(),
                         server->GetNextSerial(), server->GetNextTime(),
                         30 /* a */, WL_KEYBOARD_KEY_STATE_PRESSED);
  });

  ASSERT_TRUE(event->IsKeyEvent());
  VerifyAndClearExpectations();

  EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
  event.reset();
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_keyboard_send_key(server->seat()->keyboard()->resource(),
                         server->GetNextSerial(), server->GetNextTime(),
                         30 /* a */, WL_KEYBOARD_KEY_STATE_RELEASED);
  });

  ASSERT_TRUE(event->IsKeyEvent());

  // Setting capture doesn't affect the kbd events.
  menu_window->SetCapture();
  VerifyCanDispatchKeyEvents({menu_window.get()}, {window_.get()});

  EXPECT_CALL(menu_window_delegate, DispatchEvent(_)).Times(0);
  event.reset();
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_keyboard_send_key(server->seat()->keyboard()->resource(),
                         server->GetNextSerial(), server->GetNextTime(),
                         30 /* a */, WL_KEYBOARD_KEY_STATE_PRESSED);
  });

  ASSERT_TRUE(event->IsKeyEvent());
  VerifyAndClearExpectations();

  menu_window.reset();
}

// Tests that event is processed by the surface that has the focus. More
// extensive tests are located in wayland touch/keyboard/pointer unittests.
TEST_P(WaylandWindowTest, CanDispatchEvent) {
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  gfx::AcceleratedWidget menu_window_widget;
  EXPECT_CALL(menu_window_delegate, OnAcceleratedWidgetAvailable(_))
      .WillOnce(SaveArg<0>(&menu_window_widget));

  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10), &menu_window_delegate,
      widget_);
  EXPECT_TRUE(menu_window);

  MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
      connection_.get());
  std::unique_ptr<WaylandWindow> nested_menu_window =
      CreateWaylandWindowWithParams(
          PlatformWindowType::kMenu, gfx::Rect(20, 0, 10, 10),
          &nested_menu_window_delegate, menu_window_widget);
  EXPECT_TRUE(nested_menu_window);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER |
                                  WL_SEAT_CAPABILITY_KEYBOARD |
                                  WL_SEAT_CAPABILITY_TOUCH);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  ASSERT_TRUE(connection_->seat()->touch());
  ASSERT_TRUE(connection_->seat()->keyboard());

  // Test that CanDispatchEvent is set correctly.
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
    wl_pointer_send_enter(server->seat()->pointer()->resource(),
                          server->GetNextSerial(), toplevel_surface->resource(),
                          0, 0);
  });

  // Only |window_| can dispatch MouseEvents.
  VerifyCanDispatchMouseEvents(window_.get(),
                               {menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchTouchEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchKeyEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
    wl::ScopedWlArray empty({});
    wl_keyboard_send_enter(server->seat()->keyboard()->resource(),
                           server->GetNextSerial(),
                           toplevel_surface->resource(), empty.get());
  });

  // Only |window_| can dispatch MouseEvents and KeyEvents.
  VerifyCanDispatchMouseEvents(window_.get(),
                               {menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchTouchEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchKeyEvents({window_.get()},
                             {menu_window.get(), nested_menu_window.get()});

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
    wl_touch_send_down(server->seat()->touch()->resource(),
                       server->GetNextSerial(), 0, toplevel_surface->resource(),
                       0 /* id */, wl_fixed_from_int(50),
                       wl_fixed_from_int(100));
    wl_touch_send_frame(server->seat()->touch()->resource());
  });

  // Only |window_| can dispatch MouseEvents and KeyEvents.
  VerifyCanDispatchMouseEvents(window_.get(),
                               {menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchTouchEvents({window_.get()},
                               {menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchKeyEvents({window_.get()},
                             {menu_window.get(), nested_menu_window.get()});

  const uint32_t menu_window_surface_id =
      menu_window->root_surface()->get_surface_id();
  PostToServerAndWait(
      [toplevel_surface_id = surface_id_,
       menu_window_surface_id](wl::TestWaylandServerThread* server) {
        wl::MockSurface* toplevel_surface =
            server->GetObject<wl::MockSurface>(toplevel_surface_id);
        wl::MockSurface* menu_window_surface =
            server->GetObject<wl::MockSurface>(menu_window_surface_id);
        wl_pointer_send_leave(server->seat()->pointer()->resource(),
                              server->GetNextSerial(),
                              toplevel_surface->resource());
        wl_pointer_send_enter(server->seat()->pointer()->resource(),
                              server->GetNextSerial(),
                              menu_window_surface->resource(), 0, 0);
        wl_touch_send_up(server->seat()->touch()->resource(),
                         server->GetNextSerial(), 1000, 0 /* id */);
        wl_touch_send_frame(server->seat()->touch()->resource());

        wl_keyboard_send_leave(server->seat()->keyboard()->resource(),
                               server->GetNextSerial(),
                               toplevel_surface->resource());
      });

  // Only |menu_window| can dispatch MouseEvents.
  VerifyCanDispatchMouseEvents(menu_window.get(),
                               {window_.get(), nested_menu_window.get()});
  VerifyCanDispatchTouchEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchKeyEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});

  const uint32_t nested_menu_window_surface_id =
      nested_menu_window->root_surface()->get_surface_id();
  PostToServerAndWait([nested_menu_window_surface_id, menu_window_surface_id](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* menu_window_surface =
        server->GetObject<wl::MockSurface>(menu_window_surface_id);
    wl::MockSurface* nested_menu_window_surface =
        server->GetObject<wl::MockSurface>(nested_menu_window_surface_id);

    wl_pointer_send_leave(server->seat()->pointer()->resource(),
                          server->GetNextSerial(),
                          menu_window_surface->resource());
    wl_pointer_send_enter(server->seat()->pointer()->resource(),
                          server->GetNextSerial(),
                          nested_menu_window_surface->resource(), 0, 0);
  });

  // Only |nested_menu_window| can dispatch MouseEvents.
  VerifyCanDispatchMouseEvents(nested_menu_window.get(),
                               {window_.get(), menu_window.get()});
  VerifyCanDispatchTouchEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});
  VerifyCanDispatchKeyEvents(
      {}, {window_.get(), menu_window.get(), nested_menu_window.get()});
}

TEST_P(WaylandWindowTest, DispatchWindowMove) {
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    // Focus and press left mouse button, so that serial is sent to client.
    auto* pointer_resource = server->seat()->pointer()->resource();
    wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
                          surface->resource(), 0, 0);
    wl_pointer_send_button(pointer_resource, server->GetNextSerial(),
                           server->GetNextTime(), BTN_LEFT,
                           WL_POINTER_BUTTON_STATE_PRESSED);
  });

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    auto* xdg_surface = surface->xdg_surface();
    EXPECT_CALL(*xdg_surface->xdg_toplevel(), Move(_));
  });
  ui::GetWmMoveResizeHandler(*window_)->DispatchHostWindowDragMovement(
      HTCAPTION, gfx::Point());
}

// Makes sure hit tests are converted into right edges.
TEST_P(WaylandWindowTest, DispatchWindowResize) {
  std::vector<int> hit_test_values;
  InitializeWithSupportedHitTestValues(&hit_test_values);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });

  ASSERT_TRUE(connection_->seat()->pointer());

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    // Focus and press left mouse button, so that serial is sent to client.
    auto* pointer_resource = server->seat()->pointer()->resource();
    wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
                          surface->resource(), 0, 0);
    wl_pointer_send_button(pointer_resource, server->GetNextSerial(),
                           server->GetNextTime(), BTN_LEFT,
                           WL_POINTER_BUTTON_STATE_PRESSED);
  });

  auto* wm_move_resize_handler = ui::GetWmMoveResizeHandler(*window_);
  for (const int value : hit_test_values) {
    {
      uint32_t direction = wl::IdentifyDirection(value);
      PostToServerAndWait(
          [id = surface_id_, direction](wl::TestWaylandServerThread* server) {
            wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
            auto* xdg_surface = surface->xdg_surface();
            EXPECT_CALL(*xdg_surface->xdg_toplevel(), Resize(_, Eq(direction)));
          });
      wm_move_resize_handler->DispatchHostWindowDragMovement(value,
                                                             gfx::Point());
    }
  }
}

class WaylandWindowTestNoFractionalScale : public WaylandWindowTest {
 public:
  WaylandWindowTestNoFractionalScale() = default;
  ~WaylandWindowTestNoFractionalScale() override = default;

  WaylandWindowTestNoFractionalScale(
      const WaylandWindowTestNoFractionalScale&) = delete;
  WaylandWindowTestNoFractionalScale& operator=(
      const WaylandWindowTestNoFractionalScale&) = delete;
};

TEST_P(WaylandWindowTestNoFractionalScale, ToplevelWindowUpdateWindowScale) {
  VerifyAndClearExpectations();

  // Surface scale must be 1 when no output has been entered by the window.
  EXPECT_EQ(1, window_->applied_state().window_scale);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Configure first output with scale 1.
    wl::TestOutput* output1 = server->output();
    output1->SetPhysicalAndLogicalBounds({1920, 1080});
    output1->Flush();

    // Creating an output with scale 2.
    auto* output2 =
        server->CreateAndInitializeOutput(wl::TestOutputMetrics({1920, 1080}));
    output2->SetScale(2);
  });
  WaitForAllDisplaysReady();

  auto* output_manager = connection_->wayland_output_manager();
  EXPECT_EQ(2u, output_manager->GetAllOutputs().size());

  const uint32_t output_id1 =
      GetObjIdForOutput(output_manager->GetAllOutputs().begin()->first);
  PostToServerAndWait(
      [id = surface_id_, output_id1](wl::TestWaylandServerThread* server) {
        wl::TestOutput* output = server->GetObject<wl::TestOutput>(output_id1);
        ASSERT_TRUE(output);

        // Send the window to |output1|.
        wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(surface);
        wl_surface_send_enter(surface->resource(), output->resource());
      });

  // The window's scale and bounds must remain unchanged.
  EXPECT_EQ(1, window_->applied_state().window_scale);
  EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
  EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());

  // Get another output's id.
  const uint32_t output_id2 =
      GetObjIdForOutput(output_manager->GetAllOutputs().rbegin()->first);

  PostToServerAndWait([id = surface_id_, output_id2,
                       output_id1](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(surface);
    wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(output_id2);
    ASSERT_TRUE(output2);
    wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(output_id1);
    ASSERT_TRUE(output1);
    // Simulating drag process from |output1| to |output2|.
    wl_surface_send_enter(surface->resource(), output2->resource());
    wl_surface_send_leave(surface->resource(), output1->resource());
  });

  // The window must change its scale and bounds to keep DIP bounds the same.
  EXPECT_EQ(2, window_->applied_state().window_scale);
  EXPECT_EQ(gfx::Size(1600, 1200), window_->applied_state().size_px);
  EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
}

TEST_P(WaylandWindowTestNoFractionalScale, WaylandPopupSurfaceScale) {
  VerifyAndClearExpectations();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Configure an output with scale 1.
    wl::TestOutput* output1 = server->output();
    output1->SetPhysicalAndLogicalBounds({1920, 1080});
    output1->Flush();

    // Creating an output with scale 2.
    auto* output2 = server->CreateAndInitializeOutput(
        wl::TestOutputMetrics({1920, 0, 1920, 1080}));
    output2->SetScale(2);
  });
  WaitForAllDisplaysReady();

  auto* output_manager = connection_->wayland_output_manager();
  EXPECT_EQ(2u, output_manager->GetAllOutputs().size());

  const uint32_t output_id1 =
      GetObjIdForOutput(output_manager->GetAllOutputs().begin()->first);
  const uint32_t output_id2 =
      GetObjIdForOutput(output_manager->GetAllOutputs().rbegin()->first);

  std::vector<PlatformWindowType> window_types{PlatformWindowType::kMenu,
                                               PlatformWindowType::kTooltip};
  for (const auto& type : window_types) {
    PostToServerAndWait([id = surface_id_,
                         output_id1](wl::TestWaylandServerThread* server) {
      // Send the window to |output1|.
      wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
      ASSERT_TRUE(surface);
      wl::TestOutput* output = server->GetObject<wl::TestOutput>(output_id1);
      wl_surface_send_enter(surface->resource(), output->resource());
    });

    // Creating a wayland_popup on |window_|.
    SetPointerFocusedWindow(window_.get());
    gfx::Rect wayland_popup_bounds(15, 15, 10, 10);
    auto wayland_popup = CreateWaylandWindowWithParams(
        type, wayland_popup_bounds, &delegate_, window_->GetWidget());
    EXPECT_TRUE(wayland_popup);
    wayland_popup->Show(false);

    // the wayland_popup window should inherit its buffer scale from the focused
    // window.
    EXPECT_EQ(1, window_->applied_state().window_scale);
    EXPECT_EQ(window_->applied_state().window_scale,
              wayland_popup->applied_state().window_scale);
    EXPECT_EQ(wayland_popup_bounds.size(),
              wayland_popup->applied_state().size_px);
    EXPECT_EQ(wayland_popup_bounds, wayland_popup->GetBoundsInDIP());
    wayland_popup->Hide();

    PostToServerAndWait([id = surface_id_, output_id1,
                         output_id2](wl::TestWaylandServerThread* server) {
      wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
      ASSERT_TRUE(surface);
      wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(output_id1);
      wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(output_id2);
      // Send the window to |output2|.
      wl_surface_send_enter(surface->resource(), output2->resource());
      wl_surface_send_leave(surface->resource(), output1->resource());
    });

    EXPECT_EQ(2, window_->applied_state().window_scale);
    wayland_popup->Show(false);

    WaylandTestBase::SyncDisplay();

    // |wayland_popup|'s scale and bounds must change whenever its parents
    // scale is changed.
    EXPECT_EQ(window_->applied_state().window_scale,
              wayland_popup->applied_state().window_scale);
    EXPECT_EQ(
        gfx::ScaleToCeiledSize(wayland_popup_bounds.size(),
                               wayland_popup->applied_state().window_scale),
        wayland_popup->applied_state().size_px);

    wayland_popup->Hide();
    SetPointerFocusedWindow(nullptr);

    PostToServerAndWait([id = surface_id_,
                         output_id2](wl::TestWaylandServerThread* server) {
      wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
      ASSERT_TRUE(surface);
      wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(output_id2);
      wl_surface_send_leave(surface->resource(), output2->resource());
    });
  }
}

// Tests that WaylandPopup is able to translate provided bounds via
// PlatformWindowProperties using buffer scale it's going to use that the client
// is not able to determine before PlatformWindow is created. See
// WaylandPopup::OnInitialize for more details.
TEST_P(WaylandWindowTestNoFractionalScale, WaylandPopupInitialBufferScale) {
  VerifyAndClearExpectations();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Configure an output with scale 1.
    wl::TestOutput* main_output = server->output();
    main_output->SetPhysicalAndLogicalBounds({1920, 1080});
    main_output->Flush();

    // Creating an output with scale 1.
    server->CreateAndInitializeOutput(
        wl::TestOutputMetrics({1921, 0, 1920, 1080}));
  });

  auto* output_manager = connection_->wayland_output_manager();
  EXPECT_EQ(2u, output_manager->GetAllOutputs().size());

  const WaylandOutput* main_output =
      output_manager->GetAllOutputs().begin()->second.get();
  const WaylandOutput* secondary_output =
      output_manager->GetAllOutputs().rbegin()->second.get();

  struct {
    raw_ptr<const WaylandOutput> output;
    const char* label;
  } screen[] = {{main_output, "main output"},
                {secondary_output, "secondary output"}};

  for (const auto& entered_output : screen) {
    PostToServerAndWait(
        [id = surface_id_, entered_output_id = GetObjIdForOutput(
                               entered_output.output->output_id())](
            wl::TestWaylandServerThread* server) {
          wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
          ASSERT_TRUE(surface);
          wl::TestOutput* output =
              server->GetObject<wl::TestOutput>(entered_output_id);
          wl_surface_send_enter(surface->resource(), output->resource());
        });
    for (auto main_output_scale = 1; main_output_scale < 5;
         main_output_scale++) {
      for (auto secondary_output_scale = 1; secondary_output_scale < 5;
           secondary_output_scale++) {
        PostToServerAndWait(
            [main_output_id = GetObjIdForOutput(main_output->output_id()),
             secondary_output_id =
                 GetObjIdForOutput(secondary_output->output_id()),
             main_output_scale,
             secondary_output_scale](wl::TestWaylandServerThread* server) {
              wl::TestOutput* main_output =
                  server->GetObject<wl::TestOutput>(main_output_id);
              wl::TestOutput* secondary_output =
                  server->GetObject<wl::TestOutput>(secondary_output_id);
              // Update scale factors first.
              main_output->SetScale(main_output_scale);
              secondary_output->SetScale(secondary_output_scale);

              main_output->Flush();
              secondary_output->Flush();
            });

        gfx::Rect bounds_dip(15, 15, 10, 10);
        // DesktopWindowTreeHostPlatform uses the scale of the current display
        // of the parent window to translate initial bounds of the popup to
        // pixels.
        const int32_t effective_scale = entered_output.output->scale_factor();
        gfx::Transform transform;
        transform.Scale(effective_scale, effective_scale);
        gfx::Rect wayland_popup_bounds = transform.MapRect(bounds_dip);

        std::unique_ptr<WaylandWindow> wayland_popup =
            CreateWaylandWindowWithParams(PlatformWindowType::kMenu, bounds_dip,
                                          &delegate_, window_->GetWidget());
        EXPECT_TRUE(wayland_popup);

        wayland_popup->Show(false);

        gfx::Size expected_px_size = wayland_popup_bounds.size();
        if (entered_output.output == secondary_output) {
          expected_px_size =
              gfx::ScaleToCeiledSize(bounds_dip.size(), secondary_output_scale);
        }

        EXPECT_EQ(expected_px_size, wayland_popup->applied_state().size_px)
            << " when the window is on " << entered_output.label
            << " that has scale " << entered_output.output->scale_factor();
      }
    }
    PostToServerAndWait(
        [id = surface_id_,
         entered_output_id = GetObjIdForOutput(main_output->output_id())](
            wl::TestWaylandServerThread* server) {
          wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
          ASSERT_TRUE(surface);
          wl::TestOutput* output =
              server->GetObject<wl::TestOutput>(entered_output_id);
          wl_surface_send_leave(surface->resource(), output->resource());
        });
  }
}

TEST_P(WaylandWindowTest, WaylandPopupInitialBufferUsesParentScale) {
  VerifyAndClearExpectations();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Configure the the first output with scale 1.
    wl::TestOutput* main_output = server->output();
    main_output->SetPhysicalAndLogicalBounds({1920, 1080});
    main_output->Flush();

    // Creating an output with scale 2.
    auto* output2 = server->CreateAndInitializeOutput(
        wl::TestOutputMetrics({1921, 0, 1920, 1080}));
    output2->SetScale(2);
  });
  WaitForAllDisplaysReady();

  auto* output_manager = connection_->wayland_output_manager();
  EXPECT_EQ(2u, output_manager->GetAllOutputs().size());

  const uint32_t secondary_output_id =
      GetObjIdForOutput(output_manager->GetAllOutputs().rbegin()->first);

  PostToServerAndWait([id = surface_id_, secondary_output_id](
                          wl::TestWaylandServerThread* server) {
    // Send the window to |output2|.
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(surface);
    wl::TestOutput* output =
        server->GetObject<wl::TestOutput>(secondary_output_id);

    wl_surface_send_enter(surface->resource(), output->resource());
    if (surface->fractional_scale()) {
      surface->fractional_scale()->SendPreferredScale(2.f);
    }
  });

  constexpr gfx::Rect kBoundsDip{50, 50, 100, 100};
  const gfx::Size expected_size_px =
      gfx::ScaleToCeiledSize(kBoundsDip.size(), 2);

  std::unique_ptr<WaylandWindow> wayland_popup = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, kBoundsDip, &delegate_, window_->GetWidget());
  EXPECT_TRUE(wayland_popup);

  wayland_popup->Show(false);

  EXPECT_EQ(expected_size_px, wayland_popup->applied_state().size_px);

  PostToServerAndWait([id = surface_id_, secondary_output_id](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(surface);
    wl::TestOutput* output =
        server->GetObject<wl::TestOutput>(secondary_output_id);
    wl_surface_send_leave(surface->resource(), output->resource());
  });
}

// Tests that a WaylandWindow uses the entered output with largest scale
// factor as the preferred output. If scale factors are equal, the very first
// entered display is used.
TEST_P(WaylandWindowTest, GetPreferredOutput) {
  VerifyAndClearExpectations();

  // Buffer scale must be 1 when no output has been entered by the window.
  EXPECT_EQ(1, window_->applied_state().window_scale);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Update first output.
    wl::TestOutput* output1 = server->output();
    output1->SetPhysicalAndLogicalBounds({1920, 1080});
    output1->Flush();

    // Creating a 2nd output.
    server->CreateAndInitializeOutput(
        wl::TestOutputMetrics({1921, 0, 1920, 1080}));
  });

  WaitForAllDisplaysReady();

  // Client side WaylandOutput ids.
  ASSERT_EQ(2u, screen_->GetAllDisplays().size());
  const uint32_t output1_id =
      screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(0).id());
  const uint32_t output2_id =
      screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(1).id());

  // Client side surface.
  WaylandSurface* wayland_surface = window_->root_surface();
  EXPECT_THAT(wayland_surface->entered_outputs(), ElementsAre());

  auto* output_manager = connection_->wayland_output_manager();
  ASSERT_EQ(2u, output_manager->GetAllOutputs().size());

  // Shared wl_output resource ids.
  const uint32_t wl_output1_id = GetObjIdForOutput(output1_id);
  const uint32_t wl_output2_id = GetObjIdForOutput(output2_id);

  PostToServerAndWait([id = surface_id_, wl_output1_id,
                       wl_output2_id](wl::TestWaylandServerThread* server) {
    // Send the window to |output1| and |output2|.
    wl_surface_send_enter(
        server->GetObject<wl::MockSurface>(id)->resource(),
        server->GetObject<wl::TestOutput>(wl_output1_id)->resource());
    wl_surface_send_enter(
        server->GetObject<wl::MockSurface>(id)->resource(),
        server->GetObject<wl::TestOutput>(wl_output2_id)->resource());
  });

  // The window entered two outputs.
  EXPECT_THAT(wayland_surface->entered_outputs(),
              ElementsAre(output1_id, output2_id));

  // The window must prefer the output that it entered first.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Create the third output.
    server->CreateAndInitializeOutput(
        wl::TestOutputMetrics({0, 1081, 1920, 1080}));
  });

  WaitForAllDisplaysReady();

  ASSERT_EQ(3u, screen_->GetAllDisplays().size());
  const uint32_t output3_id =
      screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(2).id());

  EXPECT_EQ(3u, output_manager->GetAllOutputs().size());
  const uint32_t wl_output3_id = GetObjIdForOutput(output3_id);

  PostToServerAndWait(
      [id = surface_id_, wl_output3_id](wl::TestWaylandServerThread* server) {
        // Send window into 3rd output.
        wl_surface_send_enter(
            server->GetObject<wl::MockSurface>(id)->resource(),
            server->GetObject<wl::TestOutput>(wl_output3_id)->resource());
      });

  // The window entered three outputs...
  EXPECT_THAT(wayland_surface->entered_outputs(),
              ElementsAre(output1_id, output2_id, output3_id));

  // but it still must prefer the output that it entered first.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);

  PostToServerAndWait([wl_output2_id](wl::TestWaylandServerThread* server) {
    wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(wl_output2_id);
    // Pretend that the output2 has scale factor equals to 2 now.
    output2->SetScale(2);
    output2->Flush();
  });

  // Entered outputs remain the same.
  EXPECT_THAT(wayland_surface->entered_outputs(),
              ElementsAre(output1_id, output2_id, output3_id));

  // It must be the second entered output now.
  EXPECT_EQ(2, connection_->wayland_output_manager()
                   ->GetOutput(output2_id)
                   ->scale_factor());

  // The window_ must return the output with largest scale.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output2_id);

  PostToServerAndWait([wl_output1_id](wl::TestWaylandServerThread* server) {
    wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(wl_output1_id);
    // Now, the output1 changes its scale factor to 2 as well.
    output1->SetScale(2);
    output1->Flush();
  });

  // It must be the very first output now.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);

  PostToServerAndWait([wl_output1_id](wl::TestWaylandServerThread* server) {
    wl::TestOutput* output1 = server->GetObject<wl::TestOutput>(wl_output1_id);
    // Now, the output1 changes its scale factor back to 1.
    output1->SetScale(1);
    output1->Flush();
  });

  // It must be the very the second output now.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output2_id);

  PostToServerAndWait([wl_output2_id](wl::TestWaylandServerThread* server) {
    wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(wl_output2_id);
    // All outputs have scale factor of 1. window_ prefers the output that
    // it entered first again.
    output2->SetScale(1);
    output2->Flush();
  });

  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
}

TEST_P(WaylandWindowTest, GetChildrenPreferredOutput) {
  VerifyAndClearExpectations();

  // Buffer scale must be 1 when no output has been entered by the window.
  EXPECT_EQ(1, window_->applied_state().window_scale);

  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(10, 10, 10, 10),
      &menu_window_delegate, window_->GetWidget());

  menu_window->Show(false);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    // Update the first output.
    wl::TestOutput* output1 = server->output();
    output1->SetPhysicalAndLogicalBounds({1920, 1080});
    output1->Flush();

    // Create a 2nd output.
    server->CreateAndInitializeOutput(
        wl::TestOutputMetrics({1921, 0, 1920, 1080}));
  });

  WaitForAllDisplaysReady();

  // Client side WaylandOutput ids.
  ASSERT_EQ(2u, screen_->GetAllDisplays().size());
  const uint32_t output1_id =
      screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(0).id());
  const uint32_t output2_id =
      screen_->GetOutputIdForDisplayId(screen_->GetAllDisplays().at(1).id());

  auto* output_manager = connection_->wayland_output_manager();
  ASSERT_EQ(2u, output_manager->GetAllOutputs().size());

  // Shared wl_output resource ids.
  const uint32_t wl_output1_id = GetObjIdForOutput(output1_id);
  const uint32_t wl_output2_id = GetObjIdForOutput(output2_id);

  // Client side surfaces.
  WaylandSurface* root_surface = window_->root_surface();
  WaylandSurface* menu_surface = menu_window->root_surface();
  const uint32_t menu_surface_id = menu_surface->get_surface_id();

  // Neither surface should have entered any output.
  EXPECT_THAT(root_surface->entered_outputs(), ElementsAre());
  EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre());

  // Send the toplevel window into output1.
  PostToServerAndWait(
      [id = surface_id_, wl_output1_id](wl::TestWaylandServerThread* server) {
        wl_surface_send_enter(
            server->GetObject<wl::MockSurface>(id)->resource(),
            server->GetObject<wl::TestOutput>(wl_output1_id)->resource());
      });

  // The toplevel window entered the output.
  EXPECT_THAT(root_surface->entered_outputs(), ElementsAre(output1_id));
  EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre());

  // The menu also thinks it entered the same output.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
            menu_window->GetPreferredEnteredOutputId());

  // Send the menu window into output2, while the toplevel is still on output1.
  PostToServerAndWait(
      [menu_surface_id, wl_output2_id](wl::TestWaylandServerThread* server) {
        wl_surface_send_enter(
            server->GetObject<wl::MockSurface>(menu_surface_id)->resource(),
            server->GetObject<wl::TestOutput>(wl_output2_id)->resource());
      });

  // The menu surface should be aware of the output that Wayland sent it.
  EXPECT_THAT(root_surface->entered_outputs(), ElementsAre(output1_id));
  EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre(output2_id));

  // The menu still prefers output1.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
            menu_window->GetPreferredEnteredOutputId());

  // Send the toplevel window into output2.
  PostToServerAndWait(
      [id = surface_id_, wl_output2_id](wl::TestWaylandServerThread* server) {
        wl_surface_send_enter(
            server->GetObject<wl::MockSurface>(id)->resource(),
            server->GetObject<wl::TestOutput>(wl_output2_id)->resource());
      });

  // The toplevel window has now entered 2 outputs, in chronological order.
  EXPECT_THAT(root_surface->entered_outputs(),
              ElementsAre(output1_id, output2_id));
  EXPECT_THAT(menu_surface->entered_outputs(), ElementsAre(output2_id));

  // With the same scale factor, the toplevel window prefers the earlier output.
  // The menu window always prefers whatever the parent prefers.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output1_id);
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
            menu_window->GetPreferredEnteredOutputId());

  PostToServerAndWait([wl_output2_id](wl::TestWaylandServerThread* server) {
    wl::TestOutput* output2 = server->GetObject<wl::TestOutput>(wl_output2_id);
    // Now, the output2 changes its scale factor to 2.
    output2->SetScale(2);
    output2->Flush();
  });

  // The toplevel window prefers the output with higher scale factor.
  // The menu window still prefers whatever the parent prefers.
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(), output2_id);
  EXPECT_EQ(window_->GetPreferredEnteredOutputId(),
            menu_window->GetPreferredEnteredOutputId());
}

// Tests that xdg_popup is configured with default anchor properties and bounds
// if delegate doesn't have anchor properties set.
TEST_P(WaylandWindowTest, PopupPassesDefaultAnchorInformation) {
  PopupPosition menu_window_positioner, nested_menu_window_positioner;

  menu_window_positioner = {gfx::Rect(439, 46, 1, 1), gfx::Size(287, 409),
                            XDG_POSITIONER_ANCHOR_TOP_LEFT,
                            XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
                            XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y};
  nested_menu_window_positioner = {gfx::Rect(285, 1, 1, 1), gfx::Size(305, 99),
                                   XDG_POSITIONER_ANCHOR_TOP_LEFT,
                                   XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
                                   XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y};

  auto* toplevel_window = window_.get();
  toplevel_window->SetBoundsInDIP(gfx::Rect(739, 574));

  // Case 1: properties are not provided. In this case, bounds' origin must
  // be used as anchor rect and anchor position, gravity and constraints should
  // be normal.
  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  EXPECT_CALL(menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
      .WillOnce(Return(std::nullopt));
  gfx::Rect menu_window_bounds(gfx::Point(439, 46),
                               menu_window_positioner.size);
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds, &menu_window_delegate,
      toplevel_window->GetWidget());
  EXPECT_TRUE(menu_window);

  VerifyXdgPopupPosition(menu_window.get(), menu_window_positioner);

  EXPECT_CALL(menu_window_delegate, OnBoundsChanged(_)).Times(0);
  SendConfigureEventPopup(menu_window.get(), menu_window_bounds);

  EXPECT_EQ(menu_window->GetBoundsInDIP(), menu_window_bounds);

  // Case 2: the nested menu window is positioned normally.
  MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
      connection_.get());
  gfx::Rect nested_menu_window_bounds(gfx::Point(724, 47),
                                      nested_menu_window_positioner.size);
  std::unique_ptr<WaylandWindow> nested_menu_window =
      CreateWaylandWindowWithParams(
          PlatformWindowType::kMenu, nested_menu_window_bounds,
          &nested_menu_window_delegate, menu_window->GetWidget());
  EXPECT_TRUE(nested_menu_window);

  VerifyXdgPopupPosition(nested_menu_window.get(),
                         nested_menu_window_positioner);
}

// Tests that xdg_popup is configured with anchor properties received from
// delegate.
TEST_P(WaylandWindowTest, PopupPassesSetAnchorInformation) {
  PopupPosition menu_window_positioner, nested_menu_window_positioner;

  menu_window_positioner = {gfx::Rect(468, 46, 28, 28), gfx::Size(320, 404),
                            XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT,
                            XDG_POSITIONER_GRAVITY_BOTTOM_LEFT,
                            XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y};
  nested_menu_window_positioner = {
      gfx::Rect(4, 83, 312, 1), gfx::Size(480, 294),
      XDG_POSITIONER_ANCHOR_TOP_RIGHT, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
      XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y |
          XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X};

  auto* toplevel_window = window_.get();
  toplevel_window->SetBoundsInDIP(gfx::Rect(508, 212));

  MockWaylandPlatformWindowDelegate menu_window_delegate(connection_.get());
  ui::OwnedWindowAnchor anchor = {
      gfx::Rect(menu_window_positioner.anchor_rect),
      OwnedWindowAnchorPosition::kBottomRight,
      OwnedWindowAnchorGravity::kBottomLeft,
      OwnedWindowConstraintAdjustment::kAdjustmentFlipY};
  EXPECT_CALL(menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
      .WillOnce(Return(anchor));
  gfx::Rect menu_window_bounds(gfx::Point(176, 74),
                               menu_window_positioner.size);
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds, &menu_window_delegate,
      toplevel_window->GetWidget());
  EXPECT_TRUE(menu_window);

  VerifyXdgPopupPosition(menu_window.get(), menu_window_positioner);

  MockWaylandPlatformWindowDelegate nested_menu_window_delegate(
      connection_.get());
  anchor = {{180, 157, 312, 1},
            OwnedWindowAnchorPosition::kTopRight,
            OwnedWindowAnchorGravity::kBottomRight,
            OwnedWindowConstraintAdjustment::kAdjustmentFlipY |
                OwnedWindowConstraintAdjustment::kAdjustmentFlipX};
  EXPECT_CALL(nested_menu_window_delegate, GetOwnedWindowAnchorAndRectInDIP())
      .WillOnce(Return(anchor));
  gfx::Rect nested_menu_window_bounds(gfx::Point(492, 157),
                                      nested_menu_window_positioner.size);
  std::unique_ptr<WaylandWindow> nested_menu_window =
      CreateWaylandWindowWithParams(
          PlatformWindowType::kMenu, nested_menu_window_bounds,
          &nested_menu_window_delegate, menu_window->GetWidget());
  EXPECT_TRUE(nested_menu_window);

  VerifyXdgPopupPosition(nested_menu_window.get(),
                         nested_menu_window_positioner);
}

TEST_P(WaylandWindowTest, SetBoundsResizesEmptySizes) {
  display::test::TestScreen test_screen_{/*create_display=*/true,
                                         /*register_screen=*/true};
  auto* toplevel_window = window_.get();
  toplevel_window->SetBoundsInDIP(gfx::Rect(666, 666));

  testing::NiceMock<MockWaylandPlatformWindowDelegate> popup_delegate(
      connection_.get());
  gfx::Rect menu_window_bounds(gfx::Point(0, 0), {0, 0});
  std::unique_ptr<WaylandWindow> popup = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds, &popup_delegate,
      toplevel_window->GetWidget());
  EXPECT_TRUE(popup);

  popup->SetBoundsInDIP({0, 0, 0, 0});

  VerifyXdgPopupPosition(
      popup.get(),
      {gfx::Rect(0, 0, 1, 1), gfx::Size(1, 1), XDG_POSITIONER_ANCHOR_TOP_LEFT,
       XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
       XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y});
}

TEST_P(WaylandWindowTest, SetOpaqueRegion) {
  gfx::Rect new_bounds(500, 600);
  SkIRect rect =
      SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height());
  auto state_array = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED});
  SendConfigureEvent(surface_id_, new_bounds.size(), state_array);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  VerifyAndClearExpectations();
  PostToServerAndWait(
      [id = surface_id_, new_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
      });

  new_bounds.set_size(gfx::Size(1000, 534));
  rect = SkIRect::MakeXYWH(0, 0, new_bounds.width(), new_bounds.height());
  SendConfigureEvent(surface_id_, new_bounds.size(), state_array);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  VerifyAndClearExpectations();
  PostToServerAndWait(
      [id = surface_id_, new_bounds](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface);
        EXPECT_EQ(mock_surface->opaque_region(), new_bounds);
      });
}

TEST_P(WaylandWindowTest, OnCloseRequest) {
  EXPECT_CALL(delegate_, OnCloseRequest());

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
    xdg_toplevel_send_close(xdg_surface->xdg_toplevel()->resource());
  });
}

TEST_P(WaylandWindowTest, WaylandPopupSimpleParent) {
  VerifyAndClearExpectations();

  // WaylandPopup must ignore the parent provided by aura and should always
  // use focused window instead.
  gfx::Rect wayland_popup_bounds(gfx::Point(15, 15), gfx::Size(10, 10));
  std::unique_ptr<WaylandWindow> wayland_popup = CreateWaylandWindowWithParams(
      PlatformWindowType::kTooltip, wayland_popup_bounds, &delegate_,
      window_->GetWidget());
  EXPECT_TRUE(wayland_popup);

  wayland_popup->Show(false);

  PostToServerAndWait(
      [id = wayland_popup->root_surface()->get_surface_id(),
       wayland_popup_bounds](wl::TestWaylandServerThread* server) {
        auto* mock_surface_popup = server->GetObject<wl::MockSurface>(id);
        auto* mock_xdg_popup = mock_surface_popup->xdg_surface()->xdg_popup();

        EXPECT_EQ(mock_xdg_popup->anchor_rect().origin(),
                  wayland_popup_bounds.origin());
        EXPECT_EQ(mock_surface_popup->opaque_region(),
                  gfx::Rect(wayland_popup_bounds.size()));
      });

  wayland_popup->Hide();
}

TEST_P(WaylandWindowTest, WaylandPopupNestedParent) {
  VerifyAndClearExpectations();

  gfx::Rect menu_window_bounds(gfx::Point(10, 10), gfx::Size(100, 100));
  auto menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds, &delegate_,
      window_->GetWidget());
  EXPECT_TRUE(menu_window);

  VerifyAndClearExpectations();
  SetPointerFocusedWindow(menu_window.get());

  std::vector<PlatformWindowType> window_types{PlatformWindowType::kMenu,
                                               PlatformWindowType::kTooltip};
  for (const auto& type : window_types) {
    gfx::Rect nested_wayland_popup_bounds(gfx::Point(15, 15),
                                          gfx::Size(10, 10));
    auto nested_wayland_popup =
        CreateWaylandWindowWithParams(type, nested_wayland_popup_bounds,
                                      &delegate_, menu_window->GetWidget());
    EXPECT_TRUE(nested_wayland_popup);

    VerifyAndClearExpectations();

    nested_wayland_popup->Show(false);

    PostToServerAndWait(
        [id = nested_wayland_popup->root_surface()->get_surface_id(),
         nested_wayland_popup_bounds,
         menu_window_bounds](wl::TestWaylandServerThread* server) {
          auto* mock_surface_nested = server->GetObject<wl::MockSurface>(id);
          auto* mock_xdg_popup_nested =
              mock_surface_nested->xdg_surface()->xdg_popup();

          auto new_origin = nested_wayland_popup_bounds.origin() -
                            menu_window_bounds.origin().OffsetFromOrigin();
          EXPECT_EQ(mock_xdg_popup_nested->anchor_rect().origin(), new_origin);
          EXPECT_EQ(mock_surface_nested->opaque_region(),
                    gfx::Rect(nested_wayland_popup_bounds.size()));
        });

    SetPointerFocusedWindow(nullptr);
    nested_wayland_popup->Hide();
  }
}

// Tests that size constraints returned by the `ui::PlatformWindowDelegate` are
// obeyed by the window when its bounds are set internally via its
// SetBoundsInDIP() implementation.
TEST_P(WaylandWindowTest, SizeConstraintsInternal) {
  constexpr gfx::Size kMinSize{100, 100};
  constexpr gfx::Size kMaxSize{300, 300};

  window_->SetBoundsInDIP({0, 0, 200, 200});

  gfx::Rect even_smaller_bounds(kMinSize);
  even_smaller_bounds.Inset(10);
  even_smaller_bounds.set_origin({0, 0});

  EXPECT_CALL(delegate_, GetMinimumSizeForWindow()).WillOnce(Return(kMinSize));
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));

  window_->SetBoundsInDIP(even_smaller_bounds);

  VerifyAndClearExpectations();

  gfx::Rect even_greater_bounds(kMaxSize);
  even_greater_bounds.Outset(10);
  even_greater_bounds.set_origin({0, 0});

  EXPECT_CALL(delegate_, GetMaximumSizeForWindow()).WillOnce(Return(kMaxSize));
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));

  window_->SetBoundsInDIP(even_greater_bounds);
}

// Tests that size constraints returned by the `ui::PlatformWindowDelegate` are
// obeyed by the window when its bounds are set externally via the configure
// event sent by the compositor.
TEST_P(WaylandWindowTest, SizeConstraintsExternal) {
  constexpr gfx::Size kMinSize{100, 100};
  constexpr gfx::Size kMaxSize{300, 300};

  EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
      .WillRepeatedly(Return(kMinSize));
  EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
      .WillRepeatedly(Return(kMaxSize));

  window_->SetBoundsInDIP({0, 0, 200, 200});

  auto state = InitializeWlArrayWithActivatedState();

  gfx::Rect even_smaller_bounds(kMinSize);
  even_smaller_bounds.Inset(10);
  even_smaller_bounds.set_origin({0, 0});

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));

  SendConfigureEvent(surface_id_, even_smaller_bounds.size(), state);
  VerifyAndClearExpectations();

  EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
      .WillRepeatedly(Return(kMinSize));
  EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
      .WillRepeatedly(Return(kMaxSize));

  gfx::Rect even_greater_bounds(kMaxSize);
  even_greater_bounds.Outset(10);
  even_greater_bounds.set_origin({0, 0});

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));

  SendConfigureEvent(surface_id_, even_greater_bounds.size(), state);
}

TEST_P(WaylandWindowTest, OnSizeConstraintsChanged) {
  const bool kBooleans[] = {false, true};
  for (bool has_min_size : kBooleans) {
    for (bool has_max_size : kBooleans) {
      std::optional<gfx::Size> min_size =
          has_min_size ? std::optional<gfx::Size>(gfx::Size(100, 200))
                       : std::nullopt;
      std::optional<gfx::Size> max_size =
          has_max_size ? std::optional<gfx::Size>(gfx::Size(300, 400))
                       : std::nullopt;
      EXPECT_CALL(delegate_, GetMinimumSizeForWindow())
          .WillOnce(Return(min_size));
      EXPECT_CALL(delegate_, GetMaximumSizeForWindow())
          .WillOnce(Return(max_size));

      PostToServerAndWait([id = surface_id_, has_min_size,
                           has_max_size](wl::TestWaylandServerThread* server) {
        auto* mock_surface = server->GetObject<wl::MockSurface>(id);
        auto* mock_xdg_surface = mock_surface->xdg_surface();
        EXPECT_CALL(*mock_xdg_surface->xdg_toplevel(), SetMinSize(100, 200))
            .Times(has_min_size ? 1 : 0);
        EXPECT_CALL(*mock_xdg_surface->xdg_toplevel(), SetMaxSize(300, 400))
            .Times(has_max_size ? 1 : 0);
      });

      window_->SizeConstraintsChanged();
      VerifyAndClearExpectations();
    }
  }
}

TEST_P(WaylandWindowTest, DestroysCreatesSurfaceOnHideShow) {
  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  auto window = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
                                              gfx::Rect(100, 100), &delegate);
  ASSERT_TRUE(window);

  const uint32_t surface_id = window->root_surface()->get_surface_id();
  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_TRUE(mock_surface->xdg_surface());
    EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
  });

  window->Hide();

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_FALSE(mock_surface->xdg_surface());
  });

  window->Show(false);

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_TRUE(mock_surface->xdg_surface());
    EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
  });
}

TEST_P(WaylandWindowTest, DestroysCreatesPopupsOnHideShow) {
  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  auto window = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                              gfx::Rect(50, 50), &delegate,
                                              window_->GetWidget());
  ASSERT_TRUE(window);

  const uint32_t surface_id = window->root_surface()->get_surface_id();
  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_TRUE(mock_surface->xdg_surface());
    EXPECT_TRUE(mock_surface->xdg_surface()->xdg_popup());
  });

  window->Hide();

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_FALSE(mock_surface->xdg_surface());
  });

  window->Show(false);

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_TRUE(mock_surface->xdg_surface());
    EXPECT_TRUE(mock_surface->xdg_surface()->xdg_popup());
  });
}

TEST_P(WaylandWindowTest, ReattachesBackgroundOnShow) {
  EXPECT_TRUE(connection_->buffer_manager_host());

  auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
  buffer_manager_gpu_->Initialize(std::move(interface_ptr), {},
                                  /*supports_dma_buf=*/false,
                                  /*supports_viewporter=*/true,
                                  /*supports_acquire_fence=*/false,
                                  /*supports_overlays=*/true,
                                  /*supports_single_pixel_buffer=*/true);

  // Setup wl_buffers.
  constexpr uint32_t buffer_id1 = 1;
  constexpr uint32_t buffer_id2 = 2;
  gfx::Size buffer_size(1024, 768);
  auto length = 1024 * 768 * 4;
  buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
                                            buffer_id1);
  buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
                                            buffer_id2);

  base::RunLoop().RunUntilIdle();

  // Create window.
  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  auto window = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
                                              gfx::Rect(100, 100), &delegate);
  ASSERT_TRUE(window);
  auto states = InitializeWlArrayWithActivatedState();

  // Configure window to be ready to attach wl_buffers.
  const uint32_t surface_id = window->root_surface()->get_surface_id();
  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_TRUE(mock_surface->xdg_surface());
    EXPECT_TRUE(mock_surface->xdg_surface()->xdg_toplevel());
  });
  SendConfigureEvent(surface_id, {100, 100}, states);

  // Commit a frame with only background.
  std::vector<wl::WaylandOverlayConfig> overlays;
  wl::WaylandOverlayConfig background;
  background.z_order = INT32_MIN;
  background.buffer_id = buffer_id1;
  overlays.push_back(std::move(background));
  buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 1u, gfx::FrameData(),
                                      std::move(overlays));

  // Let mojo messages from gpu to host go through.
  base::RunLoop().RunUntilIdle();

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    mock_surface->SendFrameCallback();
  });

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    EXPECT_NE(mock_surface->attached_buffer(), nullptr);
  });

  window->Hide();
  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    mock_surface->SendFrameCallback();
  });

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    // `WaylandToplevelWindow::Hide()` should have already released the buffer.
    EXPECT_EQ(mock_surface->attached_buffer(), nullptr);
  });
  window->Show(false);

  SendConfigureEvent(surface_id, {100, 100}, states);

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    // Expects to receive an attach request on next frame.
    EXPECT_CALL(*mock_surface, Attach(_, _, _)).Times(1);
  });

  // Commit a frame with only the primary_plane.
  overlays.clear();
  wl::WaylandOverlayConfig primary;
  primary.z_order = 0;
  primary.buffer_id = buffer_id2;
  overlays.push_back(std::move(primary));
  buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 2u, gfx::FrameData(),
                                      std::move(overlays));

  // Let mojo messages from gpu to host go through.
  base::RunLoop().RunUntilIdle();

  PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    // WaylandWindow should automatically reattach the background.
    EXPECT_NE(mock_surface->attached_buffer(), nullptr);
  });
}

// Tests that if the window gets hidden and shown again, the title, app id and
// size constraints remain the same.
TEST_P(WaylandWindowTest, SetsPropertiesOnShow) {
  constexpr char kAppId[] = "wayland_test";
  const std::u16string kTitle(u"WaylandWindowTest");

  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(100, 100);
  properties.type = PlatformWindowType::kWindow;
  properties.wm_class_class = kAppId;

  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  auto window =
      delegate.CreateWaylandWindow(connection_.get(), std::move(properties));
  ASSERT_TRUE(window);
  window->Show(false);

  const uint32_t surface_id = window->root_surface()->get_surface_id();
  PostToServerAndWait([surface_id, app_id = window->GetWindowUniqueId()](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
    // Only app id must be set now.
    EXPECT_EQ(app_id, mock_xdg_toplevel->app_id());
    EXPECT_TRUE(mock_xdg_toplevel->title().empty());
    EXPECT_TRUE(mock_xdg_toplevel->min_size().IsEmpty());
    EXPECT_TRUE(mock_xdg_toplevel->max_size().IsEmpty());
  });

  // Now, propagate size constraints and title.
  std::optional<gfx::Size> min_size(gfx::Size(1, 1));
  std::optional<gfx::Size> max_size(gfx::Size(100, 100));
  EXPECT_CALL(delegate, GetMinimumSizeForWindow())
      .WillRepeatedly(Return(min_size));
  EXPECT_CALL(delegate, GetMaximumSizeForWindow())
      .WillRepeatedly(Return(max_size));

  PostToServerAndWait([surface_id, min_size, max_size,
                       kTitle](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
    EXPECT_CALL(*mock_xdg_toplevel, SetMinSize(min_size.value().width(),
                                               min_size.value().height()));
    EXPECT_CALL(*mock_xdg_toplevel, SetMaxSize(max_size.value().width(),
                                               max_size.value().height()));
    EXPECT_CALL(*mock_xdg_toplevel, SetTitle(base::UTF16ToUTF8(kTitle)));
  });

  window->SetTitle(kTitle);
  window->SizeConstraintsChanged();
  VerifyAndClearExpectations();

  window->Hide();

  WaylandTestBase::SyncDisplay();

  window->Show(false);

  PostToServerAndWait([surface_id, min_size, max_size, kTitle,
                       app_id = window->GetWindowUniqueId()](
                          wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
    auto* mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();

    mock_xdg_toplevel = mock_surface->xdg_surface()->xdg_toplevel();
    // We can't mock all those methods above as long as the xdg_toplevel is
    // created and destroyed on each show and hide call. However, it is the same
    // WaylandToplevelWindow object that cached the values we set and must
    // restore them on Show().
    EXPECT_EQ(mock_xdg_toplevel->min_size(), min_size.value());
    EXPECT_EQ(mock_xdg_toplevel->max_size(), max_size.value());
    EXPECT_EQ(app_id, mock_xdg_toplevel->app_id());
    EXPECT_EQ(mock_xdg_toplevel->title(), base::UTF16ToUTF8(kTitle));
  });
}

// Tests that a popup window is created using the serial of button press
// events as required by the Wayland protocol spec.
TEST_P(WaylandWindowTest, CreatesPopupOnButtonPressSerial) {
  for (bool use_explicit_grab : {false, true}) {
    base::test::ScopedCommandLine command_line_;
    if (use_explicit_grab) {
      command_line_.GetProcessCommandLine()->AppendSwitch(
          switches::kUseWaylandExplicitGrab);
    }

    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      wl_seat_send_capabilities(
          server->seat()->resource(),
          WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_KEYBOARD);
    });

    constexpr uint32_t keyboard_enter_serial = 1;
    constexpr uint32_t pointer_enter_serial = 2;
    constexpr uint32_t button_press_serial = 3;
    constexpr uint32_t button_release_serial = 4;

    PostToServerAndWait([id =
                             surface_id_](wl::TestWaylandServerThread* server) {
      wl::MockSurface* toplevel_surface =
          server->GetObject<wl::MockSurface>(id);
      wl::ScopedWlArray empty({});
      wl_keyboard_send_enter(server->seat()->keyboard()->resource(),
                             keyboard_enter_serial,
                             toplevel_surface->resource(), empty.get());

      wl_pointer_send_enter(server->seat()->pointer()->resource(),
                            pointer_enter_serial, toplevel_surface->resource(),
                            wl_fixed_from_int(0), wl_fixed_from_int(0));

      // Send two events - button down and button up.
      wl_pointer_send_button(server->seat()->pointer()->resource(),
                             button_press_serial, 1002, BTN_LEFT,
                             WL_POINTER_BUTTON_STATE_PRESSED);
      wl_pointer_send_button(server->seat()->pointer()->resource(),
                             button_release_serial, 1004, BTN_LEFT,
                             WL_POINTER_BUTTON_STATE_RELEASED);
    });

    // Create a popup window and verify the client used correct serial.
    MockWaylandPlatformWindowDelegate delegate(connection_.get());
    auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                               gfx::Rect(50, 50), &delegate,
                                               window_->GetWidget());
    ASSERT_TRUE(popup);

    const uint32_t surface_id = popup->root_surface()->get_surface_id();
    // Unfortunately, everything has to be captured as |use_explicit_grab| may
    // not be used and |maybe_unused| doesn't work with lambda captures.
    PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
      auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
      ASSERT_TRUE(test_popup);

      if (use_explicit_grab) {
        EXPECT_NE(test_popup->grab_serial(), button_release_serial);
        EXPECT_EQ(test_popup->grab_serial(), button_press_serial);
      } else {
        EXPECT_EQ(test_popup->grab_serial(), 0U);
      }
    });
  }
}

// Tests that a popup window is created using the serial of touch down events
// as required by the Wayland protocol spec.
TEST_P(WaylandWindowTest, CreatesPopupOnTouchDownSerial) {
  for (bool use_explicit_grab : {false, true}) {
    base::test::ScopedCommandLine command_line_;
    if (use_explicit_grab) {
      command_line_.GetProcessCommandLine()->AppendSwitch(
          switches::kUseWaylandExplicitGrab);
    }
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      wl_seat_send_capabilities(
          server->seat()->resource(),
          WL_SEAT_CAPABILITY_TOUCH | WL_SEAT_CAPABILITY_KEYBOARD);
    });

    constexpr uint32_t enter_serial = 1;
    constexpr uint32_t touch_down_serial = 2;
    constexpr uint32_t touch_up_serial = 3;

    PostToServerAndWait([id =
                             surface_id_](wl::TestWaylandServerThread* server) {
      wl::MockSurface* toplevel_surface =
          server->GetObject<wl::MockSurface>(id);
      wl::ScopedWlArray empty({});
      wl_keyboard_send_enter(server->seat()->keyboard()->resource(),
                             enter_serial, toplevel_surface->resource(),
                             empty.get());

      // Send two events - touch down and touch up.
      wl_touch_send_down(server->seat()->touch()->resource(), touch_down_serial,
                         0, toplevel_surface->resource(), 0 /* id */,
                         wl_fixed_from_int(50), wl_fixed_from_int(100));
      wl_touch_send_frame(server->seat()->touch()->resource());

      wl_touch_send_up(server->seat()->touch()->resource(), touch_up_serial,
                       1000, 0 /* id */);
      wl_touch_send_frame(server->seat()->touch()->resource());
    });

    // Create a popup window and verify the client used correct serial.
    MockWaylandPlatformWindowDelegate delegate(connection_.get());
    auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                               gfx::Rect(50, 50), &delegate,
                                               window_->GetWidget());
    ASSERT_TRUE(popup);

    const uint32_t surface_id = popup->root_surface()->get_surface_id();
    // Unfortunately, everything has to be captured as |use_explicit_grab| may
    // not be used and |maybe_unused| doesn't work with lambda captures.
    PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
      auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
      ASSERT_TRUE(test_popup);
      // Unless the use-wayland-explicit-grab switch is set, touch events
      // are the exception, i.e: the serial sent before the "up" event
      // (latest) cannot be used, otherwise, some compositors may dismiss
      // popups.
      if (!use_explicit_grab) {
        EXPECT_EQ(test_popup->grab_serial(), 0U);
      }
    });

    popup->Hide();

    PostToServerAndWait([id =
                             surface_id_](wl::TestWaylandServerThread* server) {
      wl::MockSurface* toplevel_surface =
          server->GetObject<wl::MockSurface>(id);
      // Send a single down event now.
      wl_touch_send_down(server->seat()->touch()->resource(), touch_down_serial,
                         0, toplevel_surface->resource(), 0 /* id */,
                         wl_fixed_from_int(50), wl_fixed_from_int(100));
      wl_touch_send_frame(server->seat()->touch()->resource());
    });

    popup->Show(false);

    // Unfortunately, everything has to be captured as |use_explicit_grab| may
    // not be used and |maybe_unused| doesn't work with lambda captures.
    PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
      auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
      ASSERT_TRUE(test_popup);

      uint32_t expected_serial = touch_down_serial;
      auto env = base::Environment::Create();
      if (base::nix::GetDesktopEnvironment(env.get()) ==
          base::nix::DESKTOP_ENVIRONMENT_GNOME) {
        // We do not grab with touch events on gnome shell.
        expected_serial = 0u;
      }
      if (use_explicit_grab) {
        EXPECT_EQ(test_popup->grab_serial(), expected_serial);
      } else {
        EXPECT_EQ(test_popup->grab_serial(), 0U);
      }
    });
  }
}

// Tests nested menu windows get the topmost window in the stack of windows
// within the same family/tree.
TEST_P(WaylandWindowTest, NestedPopupWindowsGetCorrectParent) {
  VerifyAndClearExpectations();

  gfx::Rect menu_window_bounds(gfx::Rect(10, 20, 20, 20));
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds, &delegate_,
      window_->GetWidget());
  EXPECT_TRUE(menu_window);

  EXPECT_TRUE(menu_window->parent_window() == window_.get());

  gfx::Rect menu_window_bounds2(gfx::Rect(20, 40, 30, 20));
  std::unique_ptr<WaylandWindow> menu_window2 = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds2, &delegate_,
      menu_window->GetWidget());
  EXPECT_TRUE(menu_window2);

  EXPECT_TRUE(menu_window2->parent_window() == menu_window.get());

  gfx::Rect menu_window_bounds3(gfx::Rect(30, 40, 30, 20));
  std::unique_ptr<WaylandWindow> menu_window3 = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds3, &delegate_,
      menu_window2->GetWidget());
  EXPECT_TRUE(menu_window3);

  EXPECT_TRUE(menu_window3->parent_window() == menu_window2.get());

  gfx::Rect menu_window_bounds4(gfx::Rect(40, 40, 30, 20));
  std::unique_ptr<WaylandWindow> menu_window4 = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds4, &delegate_,
      menu_window3->GetWidget());
  EXPECT_TRUE(menu_window4);

  EXPECT_TRUE(menu_window4->parent_window() == menu_window3.get());
}

TEST_P(WaylandWindowTest, DoesNotGrabPopupIfNoSeat) {
  // Create a popup window and verify the grab serial is not set.
  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  auto popup = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                             gfx::Rect(50, 50), &delegate,
                                             window_->GetWidget());
  ASSERT_TRUE(popup);

  PostToServerAndWait([surface_id = popup->root_surface()->get_surface_id()](
                          wl::TestWaylandServerThread* server) {
    auto* test_popup = GetTestXdgPopupByWindow(server, surface_id);
    ASSERT_TRUE(test_popup);
    EXPECT_EQ(test_popup->grab_serial(), 0u);
  });
}

// Regression test for https://crbug.com/1247799.
TEST_P(WaylandWindowTest, DoesNotGrabPopupUnlessParentHasGrab) {
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  SetPointerFocusedWindow(window_.get());

  // Emulate a root menu creation with no serial available and ensure
  // ozone/wayland does not attempt to grab it.
  connection_->serial_tracker().ClearForTesting();

  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  std::unique_ptr<WaylandWindow> root_menu;
  root_menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                            gfx::Rect(50, 50), &delegate,
                                            window_->GetWidget());
  VerifyAndClearExpectations();
  Mock::VerifyAndClearExpectations(&delegate);
  ASSERT_TRUE(root_menu);

  PostToServerAndWait(
      [surface_id = root_menu->root_surface()->get_surface_id()](
          wl::TestWaylandServerThread* server) {
        auto* server_root_menu = GetTestXdgPopupByWindow(server, surface_id);
        ASSERT_TRUE(server_root_menu);
        EXPECT_EQ(server_root_menu->grab_serial(), 0u);
      });

  // Emulate a nested menu creation triggered by a mouse button event and ensure
  // ozone/wayland does not attempt to grab it, as its parent also has not grab.
  EXPECT_CALL(delegate, DispatchEvent(_)).Times(2);
  PostToServerAndWait(
      [surface_id = root_menu->root_surface()->get_surface_id()](
          wl::TestWaylandServerThread* server) {
        auto* server_root_menu_surface =
            server->GetObject<wl::MockSurface>(surface_id);
        ASSERT_TRUE(server_root_menu_surface);

        auto* pointer_resource = server->seat()->pointer()->resource();
        wl_pointer_send_enter(pointer_resource, 3u /*serial*/,
                              server_root_menu_surface->resource(), 0, 0);
        wl_pointer_send_frame(pointer_resource);
        wl_pointer_send_button(pointer_resource, 4u /*serial*/, 1, BTN_LEFT,
                               WL_POINTER_BUTTON_STATE_PRESSED);
        wl_pointer_send_frame(pointer_resource);
      });
  Mock::VerifyAndClearExpectations(&delegate);

  MockWaylandPlatformWindowDelegate delegate_2(connection_.get());
  std::unique_ptr<WaylandWindow> child_menu;
  child_menu = CreateWaylandWindowWithParams(PlatformWindowType::kMenu,
                                             gfx::Rect(10, 10), &delegate_2,
                                             root_menu->GetWidget());
  VerifyAndClearExpectations();
  Mock::VerifyAndClearExpectations(&delegate_2);
  ASSERT_TRUE(child_menu);

  PostToServerAndWait(
      [surface_id = child_menu->root_surface()->get_surface_id(),
       root_menu_id = root_menu->root_surface()->get_surface_id()](
          wl::TestWaylandServerThread* server) {
        auto* server_child_menu = GetTestXdgPopupByWindow(server, surface_id);
        ASSERT_TRUE(server_child_menu);
        EXPECT_EQ(server_child_menu->grab_serial(), 0u);

        auto* pointer_resource = server->seat()->pointer()->resource();
        auto* server_root_menu_surface =
            server->GetObject<wl::MockSurface>(root_menu_id);
        wl_pointer_send_leave(pointer_resource, 5u /*serial*/,
                              server_root_menu_surface->resource());
        wl_pointer_send_frame(pointer_resource);
      });
}

TEST_P(WaylandWindowTest, InitialBounds) {
  testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate_2(
      connection_.get());
  auto toplevel = CreateWaylandWindowWithParams(
      PlatformWindowType::kWindow, gfx::Rect(10, 10, 200, 200), &delegate_2);
  {
    WaylandWindow::WindowStates window_states;
    window_states.is_maximized = false;
    window_states.is_fullscreen = false;
    window_states.is_activated = true;
    toplevel->HandleToplevelConfigureWithOrigin(0, 0, 0, 0, window_states);
  }
  toplevel->HandleSurfaceConfigure(2);
  EXPECT_EQ(gfx::Rect(10, 10, 200, 200), toplevel->GetBoundsInDIP());
}

namespace {

class WaylandSubsurfaceTest : public WaylandWindowTest {
 public:
  WaylandSubsurfaceTest() = default;
  ~WaylandSubsurfaceTest() override = default;

 protected:
  void OneWaylandSubsurfaceTestHelper(
      const gfx::RectF& subsurface_bounds,
      const gfx::RectF& expected_subsurface_bounds) {
    VerifyAndClearExpectations();

    std::unique_ptr<WaylandWindow> window = CreateWaylandWindowWithParams(
        PlatformWindowType::kWindow, gfx::Rect(640, 480), &delegate_);
    EXPECT_TRUE(window);

    bool result = window->RequestSubsurface();
    EXPECT_TRUE(result);
    connection_->Flush();

    WaylandSubsurface* wayland_subsurface =
        window->wayland_subsurfaces().begin()->get();

    PostToServerAndWait(
        [subsurface_id =
             wayland_subsurface->wayland_surface()->get_surface_id()](
            wl::TestWaylandServerThread* server) {
          auto* mock_surface_subsurface =
              server->GetObject<wl::MockSurface>(subsurface_id);
          EXPECT_TRUE(mock_surface_subsurface);
        });
    wayland_subsurface->ConfigureAndShowSurface(
        subsurface_bounds, gfx::RectF(0, 0, 640, 480) /*parent_bounds_px*/,
        1.f /*buffer_scale*/, nullptr, nullptr);
    connection_->Flush();

    PostToServerAndWait(
        [surface_id = window->root_surface()->get_surface_id(),
         subsurface_id =
             wayland_subsurface->wayland_surface()->get_surface_id(),
         expected_subsurface_bounds](wl::TestWaylandServerThread* server) {
          auto* mock_surface_root_window =
              server->GetObject<wl::MockSurface>(surface_id);
          auto* mock_surface_subsurface =
              server->GetObject<wl::MockSurface>(subsurface_id);
          EXPECT_TRUE(mock_surface_subsurface);
          auto* test_subsurface = mock_surface_subsurface->sub_surface();
          EXPECT_TRUE(test_subsurface);
          auto* parent_resource = mock_surface_root_window->resource();
          EXPECT_EQ(parent_resource, test_subsurface->parent_resource());
          // The conversion from double to fixed and back is necessary because
          // it
          // happens during the roundtrip, and it creates significant error.
          gfx::PointF expected_position(wl_fixed_to_double(wl_fixed_from_double(
                                            expected_subsurface_bounds.x())),
                                        wl_fixed_to_double(wl_fixed_from_double(
                                            expected_subsurface_bounds.y())));
          EXPECT_EQ(test_subsurface->position(), expected_position);
          EXPECT_TRUE(test_subsurface->sync());
        });
  }

  std::vector<WaylandSubsurface*> RequestWaylandSubsurface(uint32_t n) {
    VerifyAndClearExpectations();
    std::vector<WaylandSubsurface*> res =
        std::vector<WaylandSubsurface*>{window_->primary_subsurface()};
    for (uint32_t i = 0; i < n - 1; ++i) {
      window_->RequestSubsurface();
    }
    for (auto& subsurface : window_->wayland_subsurfaces()) {
      res.push_back(subsurface.get());
    }
    return res;
  }
};

}  // namespace

// Tests integer and non integer size/position support. Ozone/Wayland is
// expected to ceil the bounds.
TEST_P(WaylandSubsurfaceTest, OneWaylandSubsurfaceBoundsCeil) {
  constexpr std::array<std::array<gfx::RectF, 2>, 4> test_data = {
      {{gfx::RectF({15.12, 15.912}, {10.351, 10.742}),
        gfx::RectF({16, 16}, {11, 11})},
       {gfx::RectF({7.041, 8.583}, {13.452, 20.231}),
        gfx::RectF({8, 9}, {14, 21})},
       {gfx::RectF({15, 15}, {10, 10}), gfx::RectF({15, 15}, {10, 10})},
       {gfx::RectF({7, 8}, {16, 18}), gfx::RectF({7, 8}, {16, 18})}}};

  for (const auto& item : test_data) {
    OneWaylandSubsurfaceTestHelper(item[0] /* subsurface_bounds */,
                                   item[1] /* expected_subsurface_bounds */);
  };
}

TEST_P(WaylandSubsurfaceTest, NoDuplicateSubsurfaceRequests) {
  if (!connection_->ShouldUseOverlayDelegation()) {
    GTEST_SKIP();
  }
  auto subsurfaces = RequestWaylandSubsurface(3);
  for (auto* subsurface : subsurfaces) {
    subsurface->ConfigureAndShowSurface(gfx::RectF(1.f, 2.f, 10.f, 20.f),
                                        gfx::RectF(0.f, 0.f, 800.f, 600.f), 1.f,
                                        nullptr, nullptr);
  }
  connection_->Flush();

  PostToServerAndWait(
      [subsurface_id1 = subsurfaces[0]->wayland_surface()->get_surface_id(),
       subsurface_id2 = subsurfaces[1]->wayland_surface()->get_surface_id(),
       subsurface_id3 = subsurfaces[2]->wayland_surface()->get_surface_id()](
          wl::TestWaylandServerThread* server) {
        // From top to bottom: subsurfaces[2], subsurfaces[1], subsurfaces[0].
        std::array<wl::TestSubSurface*, 3> test_subs = {
            server->GetObject<wl::MockSurface>(subsurface_id1)->sub_surface(),
            server->GetObject<wl::MockSurface>(subsurface_id2)->sub_surface(),
            server->GetObject<wl::MockSurface>(subsurface_id3)->sub_surface(),
        };

        EXPECT_CALL(*test_subs[0], PlaceAbove(_)).Times(1);
        EXPECT_CALL(*test_subs[0], PlaceBelow(_)).Times(0);
        EXPECT_CALL(*test_subs[0], SetPosition(_, _)).Times(1);
        EXPECT_CALL(*test_subs[1], PlaceAbove(_)).Times(0);
        EXPECT_CALL(*test_subs[1], PlaceBelow(_)).Times(0);
        EXPECT_CALL(*test_subs[1], SetPosition(_, _)).Times(0);
        EXPECT_CALL(*test_subs[2], PlaceAbove(_)).Times(0);
        EXPECT_CALL(*test_subs[2], PlaceBelow(_)).Times(0);
        EXPECT_CALL(*test_subs[2], SetPosition(_, _)).Times(0);
      });

  // Stack subsurfaces[0] to be from bottom to top, and change its position.
  subsurfaces[0]->ConfigureAndShowSurface(gfx::RectF(0.f, 0.f, 10.f, 20.f),
                                          gfx::RectF(0.f, 0.f, 800.f, 600.f),
                                          1.f, subsurfaces[2], nullptr);
  subsurfaces[1]->ConfigureAndShowSurface(gfx::RectF(1.f, 2.f, 10.f, 20.f),
                                          gfx::RectF(0.f, 0.f, 800.f, 600.f),
                                          1.f, nullptr, subsurfaces[2]);
  subsurfaces[2]->ConfigureAndShowSurface(gfx::RectF(1.f, 2.f, 10.f, 20.f),
                                          gfx::RectF(0.f, 0.f, 800.f, 600.f),
                                          1.f, nullptr, subsurfaces[0]);
  connection_->Flush();

  VerifyAndClearExpectations();
}

TEST_P(WaylandWindowTest, NoDuplicateViewporterRequests) {
  EXPECT_TRUE(connection_->buffer_manager_host());

  auto interface_ptr = connection_->buffer_manager_host()->BindInterface();
  buffer_manager_gpu_->Initialize(std::move(interface_ptr), {},
                                  /*supports_dma_buf=*/false,
                                  /*supports_viewporter=*/true,
                                  /*supports_acquire_fence=*/false,
                                  /*supports_overlays=*/true,
                                  /*supports_single_pixel_buffer=*/true);

  // Setup wl_buffers.
  constexpr uint32_t buffer_id = 1;
  gfx::Size buffer_size(1024, 768);
  auto length = 1024 * 768 * 4;
  buffer_manager_gpu_->CreateShmBasedBuffer(MakeFD(), length, buffer_size,
                                            buffer_id);
  base::RunLoop().RunUntilIdle();

  auto* surface = window_->root_surface();
  PostToServerAndWait([surface_id = surface->get_surface_id()](
                          wl::TestWaylandServerThread* server) {
    auto* test_viewport =
        server->GetObject<wl::MockSurface>(surface_id)->viewport();

    // Set viewport src and dst.
    EXPECT_CALL(*test_viewport, SetSource(512, 384, 512, 384)).Times(1);
    EXPECT_CALL(*test_viewport, SetDestination(800, 600)).Times(1);
  });

  surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
      surface, buffer_id));

  surface->set_buffer_crop({0.5, 0.5, 0.5, 0.5});
  surface->set_viewport_destination({800, 600});
  surface->ApplyPendingState();
  surface->Commit();
  connection_->Flush();

  VerifyAndClearExpectations();

  PostToServerAndWait([surface_id = surface->get_surface_id()](
                          wl::TestWaylandServerThread* server) {
    auto* test_viewport =
        server->GetObject<wl::MockSurface>(surface_id)->viewport();
    Mock::VerifyAndClearExpectations(test_viewport);
    // Duplicate viewport requests are not sent.
    EXPECT_CALL(*test_viewport, SetSource(_, _, _, _)).Times(0);
    EXPECT_CALL(*test_viewport, SetDestination(_, _)).Times(0);
  });

  surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
      surface, buffer_id));

  surface->set_buffer_crop({0.5, 0.5, 0.5, 0.5});
  surface->set_viewport_destination({800, 600});
  surface->ApplyPendingState();
  surface->Commit();
  connection_->Flush();

  VerifyAndClearExpectations();

  PostToServerAndWait([surface_id = surface->get_surface_id()](
                          wl::TestWaylandServerThread* server) {
    auto* test_viewport =
        server->GetObject<wl::MockSurface>(surface_id)->viewport();
    Mock::VerifyAndClearExpectations(test_viewport);
    // Unset viewport src and dst.
    EXPECT_CALL(*test_viewport, SetSource(-1, -1, -1, -1)).Times(1);
    EXPECT_CALL(*test_viewport, SetDestination(-1, -1)).Times(1);
  });

  surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
      surface, buffer_id));

  surface->set_buffer_crop({0., 0., 1., 1.});
  surface->set_viewport_destination({1024, 768});
  surface->ApplyPendingState();
  surface->Commit();
  connection_->Flush();

  VerifyAndClearExpectations();

  PostToServerAndWait([surface_id = surface->get_surface_id()](
                          wl::TestWaylandServerThread* server) {
    auto* test_viewport =
        server->GetObject<wl::MockSurface>(surface_id)->viewport();
    Mock::VerifyAndClearExpectations(test_viewport);
    // Duplicate viewport requests are not sent.
    EXPECT_CALL(*test_viewport, SetSource(_, _, _, _)).Times(0);
    EXPECT_CALL(*test_viewport, SetDestination(_, _)).Times(0);
  });

  surface->AttachBuffer(connection_->buffer_manager_host()->EnsureBufferHandle(
      surface, buffer_id));

  surface->set_buffer_crop({0., 0., 1., 1.});
  surface->set_viewport_destination({1024, 768});
  surface->ApplyPendingState();
  surface->Commit();
  connection_->Flush();

  VerifyAndClearExpectations();
}

// Tests that WaylandPopups can be repositioned.
TEST_P(WaylandWindowTest, RepositionPopups) {
  VerifyAndClearExpectations();

  gfx::Rect menu_window_bounds(gfx::Rect(6, 20, 8, 20));
  std::unique_ptr<WaylandWindow> menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, menu_window_bounds, &delegate_,
      window_->GetWidget());
  EXPECT_TRUE(menu_window);
  EXPECT_TRUE(menu_window->IsVisible());

  PostToServerAndWait(
      [id = menu_window->root_surface()->get_surface_id(),
       menu_window_bounds](wl::TestWaylandServerThread* server) {
        auto* mock_surface_popup = server->GetObject<wl::MockSurface>(id);
        auto* mock_xdg_popup = mock_surface_popup->xdg_surface()->xdg_popup();

        EXPECT_EQ(mock_xdg_popup->anchor_rect().origin(),
                  menu_window_bounds.origin());
        EXPECT_EQ(mock_xdg_popup->size(), menu_window_bounds.size());
        EXPECT_EQ(mock_surface_popup->opaque_region(),
                  gfx::Rect(menu_window_bounds.size()));
      });

  VerifyAndClearExpectations();

  const gfx::Rect damage_rect{menu_window_bounds.width(),
                              menu_window_bounds.height()};
  EXPECT_CALL(delegate_, OnDamageRect(Eq(damage_rect))).Times(1);
  menu_window_bounds.set_origin({10, 10});
  menu_window->SetBoundsInDIP(menu_window_bounds);

  PostToServerAndWait(
      [id = menu_window->root_surface()->get_surface_id(),
       menu_window_bounds](wl::TestWaylandServerThread* server) {
        // Xdg objects can be recreated depending on the version of the xdg
        // shell.
        auto* mock_surface_popup = server->GetObject<wl::MockSurface>(id);
        auto* mock_xdg_popup = mock_surface_popup->xdg_surface()->xdg_popup();

        EXPECT_EQ(mock_xdg_popup->anchor_rect().origin(),
                  menu_window_bounds.origin());
        EXPECT_EQ(mock_xdg_popup->size(), menu_window_bounds.size());
        EXPECT_EQ(mock_surface_popup->opaque_region(),
                  gfx::Rect(menu_window_bounds.size()));
      });

  // This will send a configure event for the xdg_surface that backs the
  // xdg_popup. Size and state are not used there.
  SendConfigureEvent(menu_window->root_surface()->get_surface_id(), {0, 0},
                     wl::ScopedWlArray({}));

  VerifyAndClearExpectations();
}

// If buffers are not attached (aka WaylandBufferManagerHost is not used for
// buffer management), WaylandSurface::Commit mustn't result in creation of
// surface sync.
TEST_P(WaylandWindowTest, DoesNotCreateSurfaceSyncOnCommitWithoutBuffers) {
  EXPECT_THAT(window_->root_surface()->surface_sync_, nullptr);
  window_->root_surface()->Commit();
  EXPECT_THAT(window_->root_surface()->surface_sync_, nullptr);
}

void WaylandWindowTest::DoStartWithMinimizedTest(bool async_state) {
  // Make sure the window is initialized to normal state from the beginning.
  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());

  SendConfigureEvent(surface_id_, {0, 0},
                     InitializeWlArrayWithActivatedState());
  AdvanceFrameToCurrent(window_.get(), delegate_);

  WaylandTestBase::SyncDisplay();

  if (async_state) {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
    window_->Minimize();
    VerifyAndClearExpectations();

    // The state of the window must remain normal until the Configure event.
    EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());

    // Only after the Configure event is sent, the OnWindowStateChanged should
    // be called and the state changed.
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
  } else {
    EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
    window_->Minimize();
  }

  wl::ScopedWlArray empty_state({});
  SendConfigureEvent(surface_id_, gfx::Size(800, 600), empty_state);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  VerifyAndClearExpectations();

  // The state of the window has to be already minimized.
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);

  // We don't receive any state change if that does not differ from the last
  // state.
  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(0);
  // It must be still the same minimized state.
  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kMinimized);

  EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());

  // The window geometry has to be set to the current bounds of the window for
  // minimized state.
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* surface = server->GetObject<wl::MockSurface>(id);
    auto* xdg_surface = surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
  });
  // Send one additional empty configuration event for minimized state.
  // (which means the surface is not maximized, fullscreen or activated)
  SendConfigureEvent(surface_id_, {0, 0}, wl::ScopedWlArray({}));
}

TEST_P(WaylandWindowTest, StartWithMinimized) {
  DoStartWithMinimizedTest(/*async_state=*/false);
}

TEST_P(WaylandWindowTest, StartWithMinimizedAsync) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kAsyncFullscreenWindowState);
  DoStartWithMinimizedTest(/*async_state=*/true);
}

class BlockableWaylandToplevelWindow : public WaylandToplevelWindow {
 public:
  BlockableWaylandToplevelWindow(MockWaylandPlatformWindowDelegate* delegate,
                                 WaylandConnection* connection)
      : WaylandToplevelWindow(delegate, connection) {}

  static std::unique_ptr<BlockableWaylandToplevelWindow> Create(
      const gfx::Rect& bounds,
      WaylandConnection* connection,
      MockWaylandPlatformWindowDelegate* delegate) {
    auto window =
        std::make_unique<BlockableWaylandToplevelWindow>(delegate, connection);

    PlatformWindowInitProperties properties;
    properties.bounds = bounds;
    properties.type = PlatformWindowType::kWindow;
    properties.parent_widget = gfx::kNullAcceleratedWidget;
    window->Initialize(std::move(properties));
    window->Show(false);
    return window;
  }

  // WaylandToplevelWindow overrides:
  uint32_t DispatchEvent(const PlatformEvent& platform_event) override {
    ui::Event* event(platform_event);
    if (event->type() == EventType::kTouchReleased && !blocked_) {
      base::RunLoop run_loop{base::RunLoop::Type::kNestableTasksAllowed};
      blocked_ = true;

      base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(async_task_), run_loop.QuitClosure()));
      run_loop.Run();
      blocked_ = false;
    }

    return WaylandToplevelWindow::DispatchEvent(platform_event);
  }

  void SetAsyncTask(
      base::RepeatingCallback<void(base::OnceClosure)> async_task) {
    async_task_ = std::move(async_task);
  }

 private:
  bool blocked_ = false;
  base::RepeatingCallback<void(base::OnceClosure)> async_task_;
};

// This test ensures that Ozone/Wayland does not crash while handling a
// sequence of two or more touch down/up actions, where the first one blocks
// unfinished before the second pair comes in.
//
// This mimics the behavior of a modal dialog that comes up as a result of
// the first touch down/up action, and blocks the original flow, before it gets
// handled completely.
TEST_P(WaylandWindowTest, BlockingTouchDownUp_NoCrash) {
  window_.reset();

  MockWaylandPlatformWindowDelegate delegate(connection_.get());
  auto window = BlockableWaylandToplevelWindow::Create(
      gfx::Rect(800, 600), connection_.get(), &delegate);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(
        server->seat()->resource(),
        WL_SEAT_CAPABILITY_POINTER | WL_SEAT_CAPABILITY_TOUCH);
  });
  ASSERT_TRUE(connection_->seat()->pointer());
  ASSERT_TRUE(connection_->seat()->touch());
  window->set_touch_focus(true);

  // Test that CanDispatchEvent is set correctly.
  VerifyCanDispatchTouchEvents({window.get()}, {});

  // Steps to be executed after the handling of the first touch down/up
  // pair blocks.
  auto async_task = base::BindLambdaForTesting([&](base::OnceClosure closure) {
    PostToServerAndWait([id = window->root_surface()->get_surface_id()](
                            wl::TestWaylandServerThread* server) {
      wl::MockSurface* toplevel_surface =
          server->GetObject<wl::MockSurface>(id);
      wl_touch_send_down(server->seat()->touch()->resource(),
                         server->GetNextSerial(), 0,
                         toplevel_surface->resource(), 0 /* id */,
                         wl_fixed_from_int(100), wl_fixed_from_int(100));
      wl_touch_send_up(server->seat()->touch()->resource(),
                       server->GetNextSerial(), 2000, 0 /* id */);
      wl_touch_send_frame(server->seat()->touch()->resource());
    });

    std::move(closure).Run();
  });
  window->SetAsyncTask(std::move(async_task));

  PostToServerAndWait([id = window->root_surface()->get_surface_id()](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* toplevel_surface = server->GetObject<wl::MockSurface>(id);
    // Start executing the first touch down/up pair.
    wl_touch_send_down(server->seat()->touch()->resource(),
                       server->GetNextSerial(), 0, toplevel_surface->resource(),
                       0 /* id */, wl_fixed_from_int(50),
                       wl_fixed_from_int(50));
    wl_touch_send_up(server->seat()->touch()->resource(),
                     server->GetNextSerial(), 1000, 0 /* id */);
    wl_touch_send_frame(server->seat()->touch()->resource());
  });
}

// Make sure that changing focus during dispatch will not re-dispatch the event
// to the newly focused window. (crbug.com/1339082);
// Flaky on device/VM: https://crbug.com/1348046
TEST_P(WaylandWindowTest, ChangeFocusDuringDispatch) {
  MockPlatformWindowDelegate other_delegate;
  gfx::AcceleratedWidget other_widget = gfx::kNullAcceleratedWidget;
  EXPECT_CALL(other_delegate, OnAcceleratedWidgetAvailable(_))
      .WillOnce(SaveArg<0>(&other_widget));

  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(10, 10);
  properties.type = PlatformWindowType::kWindow;
  auto other_window = WaylandWindow::Create(&other_delegate, connection_.get(),
                                            std::move(properties));
  ASSERT_NE(other_widget, gfx::kNullAcceleratedWidget);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });

  ASSERT_TRUE(connection_->seat()->pointer());

  int count = 0;
  EXPECT_CALL(other_delegate, DispatchEvent(_)).Times(1);
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly([&](Event* event) {
    count++;
    if (event->type() == ui::EventType::kMousePressed) {
      PostToServerAndWait(
          [id = surface_id_,
           other_id = other_window->root_surface()->get_surface_id()](
              wl::TestWaylandServerThread* server) {
            wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
            ASSERT_TRUE(surface);
            wl::MockSurface* other_surface =
                server->GetObject<wl::MockSurface>(other_id);
            ASSERT_TRUE(other_surface);
            auto* pointer = server->seat()->pointer();
            wl_pointer_send_leave(pointer->resource(), 3, surface->resource());
            wl_pointer_send_frame(pointer->resource());

            wl_pointer_send_enter(pointer->resource(), 4,
                                  other_surface->resource(), 0, 0);
            wl_pointer_send_frame(pointer->resource());
          });
    }
  });

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(surface);
    auto* pointer = server->seat()->pointer();

    wl_pointer_send_enter(pointer->resource(), 1, surface->resource(), 0, 0);
    // The Enter event is coupled with the frame event.
    wl_pointer_send_frame(pointer->resource());
    wl_pointer_send_button(pointer->resource(), 2, 1004, BTN_LEFT,
                           WL_POINTER_BUTTON_STATE_PRESSED);
    wl_pointer_send_frame(pointer->resource());
  });

  EXPECT_EQ(count, 3);
}

TEST_P(WaylandWindowTest, WindowMovedResized) {
  const gfx::Rect initial_bounds = window_->GetBoundsInDIP();

  gfx::Rect new_bounds(initial_bounds);
  new_bounds.set_x(new_bounds.origin().x() + 10);
  new_bounds.set_y(new_bounds.origin().y() + 10);
  // Configure is not necessary to just move.
  EXPECT_CALL(delegate_, OnBoundsChanged(BoundsChange(true)));
  PostToServerAndWait([id = surface_id_,
                       new_bounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    auto* xdg_surface = surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
        .Times(0);
  });
  window_->SetBoundsInDIP(new_bounds);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Resize and move.
  new_bounds.Inset(5);
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange(true)))).Times(1);
  PostToServerAndWait([id = surface_id_,
                       new_bounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    auto* xdg_surface = surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
        .Times(0);
  });
  window_->SetBoundsInDIP(new_bounds);
  VerifyAndClearExpectations();

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(BoundsChange(true)))).Times(1);
  PostToServerAndWait([id = surface_id_,
                       new_bounds](wl::TestWaylandServerThread* server) {
    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
    auto* xdg_surface = surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(new_bounds.size())))
        .Times(1);
  });
  wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
  SendConfigureEvent(surface_id_, new_bounds.size(), states);
  AdvanceFrameToCurrent(window_.get(), delegate_);
}

// Make sure that creating a window with DIP bounds creates a window with
// the same DIP bounds with various fractional scales.
TEST_P(WaylandWindowTest, NoRoundingErrorInDIP) {
  VerifyAndClearExpectations();
  auto* primary_output =
      connection_->wayland_output_manager()->GetPrimaryOutput();
  constexpr float kScales[] = {display::kDsf_1_777, display::kDsf_2_252,
                               display::kDsf_2_666, display::kDsf_1_8};
  for (float scale : kScales) {
    primary_output->SetScaleFactorForTesting(scale);
    // Update to delegate to use the correct scale;
    window_->UpdateWindowScale(true);

    testing::NiceMock<MockWaylandPlatformWindowDelegate> delegate(
        connection_.get());
    std::unique_ptr<WaylandWindow> wayland_window =
        CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
                                      gfx::Rect(20, 0, 100, 100), &delegate);
    for (int i = 100; i < 3000; i++) {
      const gfx::Rect kBoundsDip{20, 0, i, 3000 - i};
      const gfx::Rect bounds_in_px =
          delegate_.ConvertRectToPixels(gfx::Rect(kBoundsDip.size()));
      wayland_window->SetBoundsInDIP(kBoundsDip);
      AdvanceFrameToCurrent(wayland_window.get(), delegate);
      EXPECT_EQ(bounds_in_px.size(), wayland_window->applied_state().size_px);
      EXPECT_EQ(kBoundsDip, wayland_window->GetBoundsInDIP());
    }
  }
  VerifyAndClearExpectations();
}

// Make sure that the window scale change is applied on the latest
// in_flight_requests.
TEST_P(WaylandWindowTest, ScaleChangeWhenStateRequestThrottoled) {
  VerifyAndClearExpectations();

  gfx::Rect bounds_dip;
  auto* toplevel = static_cast<WaylandToplevelWindow*>(window_.get());
  for (int i = 300; i <= 600; i++) {
    bounds_dip = {0, 0, i, 900 - i};
    toplevel->HandleToplevelConfigure(bounds_dip.width(), bounds_dip.height(),
                                      {});
    toplevel->HandleSurfaceConfigure(i);
  }
  // latest bounds_dip is throttled, and not applied, scale factor is 1.
  EXPECT_NE(bounds_dip.size(), toplevel->applied_state().bounds_dip.size());
  EXPECT_EQ(bounds_dip.size(),
            delegate_.ConvertRectToPixels(bounds_dip).size());

  // Update to delegate to use the correct scale;
  constexpr float kScale = display::kDsf_1_777;
  auto* primary_output =
      connection_->wayland_output_manager()->GetPrimaryOutput();
  primary_output->SetScaleFactorForTesting(kScale);
  if (GetParam().supports_viewporter_surface_scaling) {
    const auto surface_id = toplevel->root_surface()->get_surface_id();
    PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
      auto* mock_surface = server->GetObject<wl::MockSurface>(surface_id);
      if (mock_surface->fractional_scale()) {
        mock_surface->fractional_scale()->SendPreferredScale(kScale);
      }
    });
  }
  toplevel->UpdateWindowScale(true);
  AdvanceFrameToCurrent(window_.get(), delegate_);

  // bounds_dip advances to be applied, and scaled correctly.
  EXPECT_EQ(bounds_dip.size(), toplevel->applied_state().bounds_dip.size());
  EXPECT_NE(bounds_dip.size(),
            delegate_.ConvertRectToPixels(bounds_dip).size());
  EXPECT_EQ(window_->applied_state().size_px,
            delegate_.ConvertRectToPixels(bounds_dip).size());
  VerifyAndClearExpectations();
}

// Tests that a re-entrant state update is handled serially by `WaylandWindow`
// and does not crash.
TEST_P(WaylandWindowTest, ReentrantApplyStateWorks) {
  constexpr gfx::Rect kBounds1{123, 234};
  constexpr gfx::Rect kBounds2{234, 345};
  constexpr gfx::Rect kBounds3{345, 456};

  PostToServerAndWait([id = surface_id_,
                       bounds = kBounds1](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(1);
    EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
  });

  window_->SetBoundsInDIP(kBounds1);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_,
                       bounds = kBounds3](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(1);
    EXPECT_CALL(*xdg_surface, AckConfigure(_)).Times(0);
  });

  delegate_.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
    window_->SetBoundsInDIP(kBounds3);
    return true;
  }));
  window_->SetBoundsInDIP(kBounds2);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();
}

// Tests that configuring twice with the same state immediately acks and
// commits.
TEST_P(WaylandWindowTest, ConfigureWithSameStateAcksAndCommitsImmediately) {
  constexpr gfx::Rect kBounds{123, 234};
  auto state = InitializeWlArrayWithActivatedState();
  constexpr uint32_t kConfigureSerial1 = 2u;
  constexpr uint32_t kConfigureSerial2 = 3u;
  constexpr uint32_t kConfigureSerial3 = 4u;

  PostToServerAndWait([id = surface_id_,
                       bounds = kBounds](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())))
        .Times(1);
    // TODO(https://crbug.com/443275579): The proper fix should not
    // AckConfigure() until the window is mapped.
    EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial1)).Times(1);
    EXPECT_CALL(*mock_surface, Commit()).Times(0);
  });

  SendConfigureEvent(surface_id_, kBounds.size(), state, kConfigureSerial1);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
    // TODO(https://crbug.com/443275579): The proper fix should not
    // AckConfigure() until the window is mapped.
    EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial2)).Times(1);
    EXPECT_CALL(*mock_surface, Commit()).Times(0);
  });

  SendConfigureEvent(surface_id_, kBounds.size(), state, kConfigureSerial2);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  // Once window is mapped, commit immediately.
  CreateBufferAndPresentAsNewFrame(window_.get(), delegate_,
                                   /*buffer_size=*/kBounds.size(),
                                   /*buffer_scale=*/1.f);

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    auto* xdg_surface = mock_surface->xdg_surface();
    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
    EXPECT_CALL(*xdg_surface, AckConfigure(kConfigureSerial3)).Times(1);
    EXPECT_CALL(*mock_surface, Commit()).Times(1);
  });

  SendConfigureEvent(surface_id_, kBounds.size(), state, kConfigureSerial3);
  VerifyAndClearExpectations();
}

// Test that creates a screen with two displays, with work areas configured to
// be side-by-side horizontally.
class MultiDisplayWaylandWindowTest : public WaylandWindowTest {
 public:
  static constexpr int64_t kPrimaryDisplayId = 1;
  static constexpr int64_t kSecondaryDisplayId = 2;
  static constexpr gfx::Rect kPrimaryDisplayBounds{0, 0, 800, 600};
  static constexpr gfx::Rect kSecondaryDisplayBounds{800, 0, 800, 600};

  // WaylandWindowTest:
  void SetUp() override {
    test_screen_.display_list().AddDisplay(
        display::Display(kPrimaryDisplayId, kPrimaryDisplayBounds),
        display::DisplayList::Type::PRIMARY);
    test_screen_.display_list().AddDisplay(
        display::Display(kSecondaryDisplayId, kSecondaryDisplayBounds),
        display::DisplayList::Type::NOT_PRIMARY);
    EXPECT_EQ(2, test_screen_.GetNumDisplays());
    WaylandWindowTest::SetUp();
  }

 private:
  display::test::TestScreen test_screen_{/*create_display=*/false,
                                         /*register_screen=*/true};
};

// Asserts new windows ignore the display for new windows if bounds have been
// explicitly specified.
TEST_P(MultiDisplayWaylandWindowTest, NewWindowsRespectInitParamBounds) {
  MockWaylandPlatformWindowDelegate delegate(connection_.get());

  // Set the secondary display as the new window target.
  const display::ScopedDisplayForNewWindows scoped_display_new_windows(
      kSecondaryDisplayId);

  // Init a new window with non-empty bounds.
  constexpr gfx::Rect kInitBounds(100, 100, 100, 100);
  const auto window = delegate.CreateWaylandWindow(
      connection_.get(), PlatformWindowInitProperties(kInitBounds));
  ASSERT_TRUE(window);

  // Assert the window is placed at the specified bounds, ignoring the display
  // for new windows.
  EXPECT_EQ(kInitBounds, window->GetBoundsInDIP());
}

TEST_P(WaylandWindowTest, UsePreferredSurfaceScale) {
  ASSERT_TRUE(connection_->UsePerSurfaceScaling());
  EXPECT_EQ(1u, screen_->GetAllDisplays().size());
  EXPECT_EQ(1.0f, screen_->GetDisplayForAcceleratedWidget(window_->GetWidget())
                      .device_scale_factor());
  EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  ASSERT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);

  // GetPreferredScaleFactorForAcceleratedWidget must return the ui scale value
  // until the preferred surface scale hasn't been received yet.
  EXPECT_FALSE(window_->GetPreferredScaleFactor().has_value());
  EXPECT_EQ(
      1.0f,
      screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
          .value_or(0.f));

  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);

  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    ASSERT_TRUE(mock_surface->fractional_scale());
    mock_surface->fractional_scale()->SendPreferredScale(1.5f);
  });

  SyncDisplay();
  Mock::VerifyAndClearExpectations(&delegate_);

  // Once preferred surface scale is received, the screen API starts returning
  // the composed scale value, ie: ui_scale * window_scale.
  EXPECT_EQ(1.5f, window_->GetPreferredScaleFactor().value_or(0.f));
  EXPECT_EQ(
      1.5f,
      screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
          .value_or(0.f));
  // The preferred scale is then reflected in state's `window_scale` when
  // notifying the bounds change.
  EXPECT_EQ(1.5f, window_->applied_state().window_scale);
  ASSERT_EQ(gfx::Size(1200, 900), window_->applied_state().size_px);

  // Ensure state (including scale and bounds) gets latched only when the
  // corresponding frame comes in.
  EXPECT_EQ(1.0f, window_->latched_state().window_scale);
  ASSERT_EQ(gfx::Size(800, 600), window_->latched_state().size_px);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  EXPECT_EQ(1.5f, window_->latched_state().window_scale);
  ASSERT_EQ(gfx::Size(1200, 900), window_->latched_state().size_px);

  // GetDisplayForAcceleratedWidget keeps returning the entered output, whose
  // scale is unchanged in this case.
  EXPECT_EQ(1.0f, screen_->GetDisplayForAcceleratedWidget(window_->GetWidget())
                      .device_scale_factor());
}

TEST_P(WaylandWindowTest, UiScale_HandleFontScaleChange) {
  ASSERT_TRUE(connection_->IsUiScaleEnabled());

  // Required for emulating mouse events.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());

  // Ensure the initial `window_` and its underlying root surface state is set
  // as expected.
  EXPECT_EQ(1.0f, window_->applied_state().ui_scale);
  EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
  EXPECT_EQ(window_->applied_state(), window_->latched_state());
  // Ensure WaylandScreen::GetPreferredScaleFactorForAcceleratedWidget returns
  // the ui scale value while preferred surface scale hasn't been received yet.
  EXPECT_FALSE(window_->GetPreferredScaleFactor().has_value());
  EXPECT_EQ(
      1.0f,
      screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
          .value_or(0.f));

  // Receiving a `wp_fractional_scale_v1::preferred_scale` with scale 1.0
  // shouldn't lead to `OnBoundsChanged` calls, as no change did actually occur.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(0);
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    ASSERT_TRUE(mock_surface->fractional_scale());
    mock_surface->fractional_scale()->SendPreferredScale(1.0f);
  });
  WaylandTestBase::SyncDisplay();
  Mock::VerifyAndClearExpectations(&delegate_);
  // Once preferred surface scale is received, the screen API starts returning
  // the composed scale value, ie: ui_scale * window_scale.
  EXPECT_EQ(1.0f, window_->GetPreferredScaleFactor().value_or(0.f));
  EXPECT_EQ(
      1.0f,
      screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
          .value_or(0.f));

  // Setting font scale to 1.25 (which usually happens when 'large-text' system
  // setting is turned on) leads to bounds change with the expectations set
  // below.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
  ASSERT_TRUE(!!connection_->window_manager());
  connection_->window_manager()->SetFontScale(1.25f);
  Mock::VerifyAndClearExpectations(&delegate_);
  // Ensure that once preferred surface scale is received, the screen
  // GetPreferredScaleFactorForAcceleratedWidget API returns the composed scale
  // value, ie: ui_scale * window_scale.
  EXPECT_EQ(1.0f, window_->GetPreferredScaleFactor().value_or(0.f));
  EXPECT_EQ(
      1.25f,
      screen_->GetPreferredScaleFactorForAcceleratedWidget(window_->GetWidget())
          .value_or(0.f));

  EXPECT_EQ(1.25f, window_->applied_state().ui_scale);
  EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  // DIP size gets downscaled by the composed scale, such that the pixel size
  // keeps the same.
  EXPECT_EQ(gfx::Size(640, 480), window_->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);
  EXPECT_EQ(window_->root_surface()->state_.buffer_scale_float, 1.0f);
  EXPECT_NE(window_->applied_state(), window_->latched_state());

  // Verifies both event dispatching and screen "cursor location" API work as
  // expected with ui_scale > 1. Regression test fo https://crbug.com/396457560.
  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillRepeatedly(CloneEvent(&event));
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    auto* pointer_resource = server->seat()->pointer()->resource();
    auto* toplevel_surface = server->GetObject<wl::MockSurface>(id);
    wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
                          toplevel_surface->resource(), 0, 0);
    wl_pointer_send_motion(pointer_resource, server->GetNextSerial(),
                           wl_fixed_from_double(100.0f),
                           wl_fixed_from_double(100.0f));
  });
  ASSERT_TRUE(event->IsMouseEvent());
  ASSERT_EQ(event->type(), ui::EventType::kMouseMoved);
  // Event dispatching API expects pixel coordinates.
  EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(100, 100));
  // Internal cursor position tracker uses Wayland DIP coordinates.
  ASSERT_TRUE(connection_->wayland_cursor_position());
  EXPECT_EQ(connection_->wayland_cursor_position()->GetCursorSurfacePoint(),
            gfx::Point(100, 100));
  // Screen API uses UI DIP coordinates.
  EXPECT_EQ(screen_->GetCursorScreenPoint(), gfx::Point(80, 80));

  // Applied state gets latched when the corresponding produced frame is
  // received from Viz and processed by `window_`s frame manager, which is
  // emulated in this test by the `CreateShmBasedBuffer` + `CommitOverlays`
  // calls below. After that, several expectations are checked, eg: latched
  // state, surface state (scale) as well as the relevant wayland requests
  // issued during the process.
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    EXPECT_CALL(*mock_surface, Damage(0, 0, 800, 600)).Times(1);
    ASSERT_TRUE(mock_surface->viewport());
    EXPECT_CALL(*mock_surface->viewport(), SetSource(_, _, _, _)).Times(0);
    EXPECT_CALL(*mock_surface->viewport(), SetDestination(_, _)).Times(0);
    ASSERT_TRUE(mock_surface->xdg_surface());
    EXPECT_CALL(*mock_surface->xdg_surface(),
                SetWindowGeometry(gfx::Rect(800, 600)));
    EXPECT_CALL(*mock_surface->xdg_surface(), AckConfigure(_)).Times(0);
  });
  CreateBufferAndPresentAsNewFrame(window_.get(), delegate_,
                                   /*buffer_size=*/gfx::Size(800, 600),
                                   /*buffer_scale=*/1.25f);
  // Sync with the test wayland compositor and verify the expectations.
  VerifyAndClearExpectations();
  EXPECT_EQ(window_->applied_state(), window_->latched_state());
  EXPECT_EQ(window_->latched_state().ui_scale, 1.25f);
  EXPECT_EQ(window_->latched_state().window_scale, 1.0f);
  EXPECT_EQ(window_->root_surface()->state_.buffer_scale_float, 1.0f);
}

TEST_P(WaylandWindowTest, UiScale_HandleServerTriggeredBoundsChange) {
  ASSERT_TRUE(connection_->IsUiScaleEnabled());

  // Initialize surface preferred scale.
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(mock_surface);
    ASSERT_TRUE(mock_surface->fractional_scale());
    mock_surface->fractional_scale()->SendPreferredScale(1.0f);
  });
  WaylandTestBase::SyncDisplay();
  EXPECT_EQ(1.0f, window_->GetPreferredScaleFactor().value_or(0));

  // Set font scale to 1.25.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
  connection_->window_manager()->SetFontScale(1.25f);
  VerifyAndClearExpectations();

  // Emulate a server-triggered bounds change.
  constexpr uint32_t kConfigureSerial = 55u;
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
  SendConfigureEvent(surface_id_, gfx::Size(1000, 1000), wl::ScopedWlArray({}),
                     kConfigureSerial);
  VerifyAndClearExpectations();
  // Ensure a bounds change request was issued, where DIP bounds is downsized
  // proportionally to `ui_scale`, while pixel bounds keeps unchanged.
  EXPECT_EQ(1.25f, window_->applied_state().ui_scale);
  EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  EXPECT_EQ(gfx::Size(800, 800), window_->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(1000, 1000), window_->applied_state().size_px);
  // Verify that correct values are used for wayland requests issued when
  // applying surface state, even though Viz' buffer scale factor is 1.25, i.e:
  // `ui_scale * window_scale`.
  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    EXPECT_CALL(*mock_surface, Damage(0, 0, 1000, 1000));
    EXPECT_CALL(*mock_surface->xdg_surface(),
                SetWindowGeometry(gfx::Rect(1000, 1000)));
    EXPECT_CALL(*mock_surface->xdg_surface(),
                AckConfigure(Eq(kConfigureSerial)));
  });
  CreateBufferAndPresentAsNewFrame(window_.get(), delegate_,
                                   /*buffer_size=*/gfx::Size(1000, 1000),
                                   /*buffer_scale=*/1.25f);
  // Sync display and verify the expectations.
  VerifyAndClearExpectations();
  EXPECT_EQ(window_->latched_state().ui_scale, 1.25f);
  EXPECT_EQ(window_->latched_state().window_scale, 1.0f);
  EXPECT_EQ(window_->root_surface()->state_.buffer_scale_float, 1.0f);
}

TEST_P(WaylandWindowTest, UiScale_InitScaleAndBounds) {
  ASSERT_TRUE(connection_->IsUiScaleEnabled());

  // Set font scale to 1.25.
  connection_->window_manager()->SetFontScale(1.25f);

  // Create a new toplelvel `window`.
  testing::NiceMock<MockWaylandPlatformWindowDelegate> new_window_delegate(
      connection_.get());
  EXPECT_CALL(new_window_delegate, OnAcceleratedWidgetAvailable(_));
  EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(0);
  PlatformWindowInitProperties properties(gfx::Rect(800, 800));
  auto new_window = new_window_delegate.CreateWaylandWindow(
      connection_.get(), std::move(properties));
  WaylandTestBase::SyncDisplay();
  Mock::VerifyAndClearExpectations(&new_window_delegate);
  const uint32_t new_window_surface_id =
      new_window->root_surface()->get_surface_id();
  ASSERT_NE(new_window_surface_id, 0u);

  // Upon initialization, even though the window scale is assumed as 1 (and
  // updated asynchronously per wayland events), the UI scale must be set to the
  // current font scale straight away (see WaylandWindow::Initialize comments
  // for more context) and pixel size must be computed based on the DIP bounds
  // passed in.
  EXPECT_EQ(gfx::Size(800, 800), new_window->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(1000, 1000), new_window->applied_state().size_px);
  EXPECT_EQ(new_window->applied_state().ui_scale, 1.25f);
  EXPECT_EQ(new_window->applied_state().window_scale, 1.0f);
  EXPECT_EQ(new_window->applied_state(), new_window->latched_state());
  PlatformWindowDelegate::State initial_state(new_window->applied_state());
  // Ensure ui_scale is returned while preferred surface scale has not been
  // received yet.
  EXPECT_EQ(1.25f, screen_
                       ->GetPreferredScaleFactorForAcceleratedWidget(
                           new_window->GetWidget())
                       .value_or(0.f));

  // Request window to be shown and verify initial state is set as expected,
  // including ui scale.
  EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(0);
  new_window->Show(/*inactive=*/false);
  Mock::VerifyAndClearExpectations(&new_window_delegate);
  CreateBufferAndPresentAsNewFrame(new_window.get(), new_window_delegate,
                                   /*buffer_size=*/gfx::Size(1000, 1000),
                                   /*buffer_scale=*/1.25f);
  VerifyAndClearExpectations(new_window_delegate, new_window_surface_id);
  EXPECT_EQ(new_window->applied_state(), initial_state);
  EXPECT_EQ(new_window->applied_state(), new_window->latched_state());
  EXPECT_EQ(new_window->root_surface()->state_.buffer_scale_float, 1.0f);

  // Emulate a wayland surface preferred fractional scale of 2.0 for
  // `new_window`.
  EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(1);
  PostToServerAndWait(
      [id = new_window_surface_id](wl::TestWaylandServerThread* server) {
        wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
        ASSERT_TRUE(mock_surface->fractional_scale());
        mock_surface->fractional_scale()->SendPreferredScale(2.0f);
      });
  VerifyAndClearExpectations(new_window_delegate, new_window_surface_id);
  EXPECT_EQ(gfx::Size(800, 800), new_window->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(2000, 2000), new_window->applied_state().size_px);
  EXPECT_EQ(new_window->applied_state().ui_scale, 1.25);
  EXPECT_EQ(new_window->applied_state().window_scale, 2.0f);
  EXPECT_EQ(2.5, screen_
                     ->GetPreferredScaleFactorForAcceleratedWidget(
                         new_window->GetWidget())
                     .value_or(0.f));

  // Send the initial activation configure events sequence, with (0, 0) size,
  // such that the client-requested size is used, i.e (1000, 1000) set above.
  // Then, emulate a new frame coming from Viz and verify the correct Wayland
  // requests and parameters are used in response to it.
  constexpr uint32_t kConfigureSerial = 11u;
  EXPECT_CALL(new_window_delegate, OnBoundsChanged(_)).Times(0);
  PostToServerAndWait([id = new_window_surface_id](
                          wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
    EXPECT_CALL(*mock_surface, Damage(0, 0, 1000, 1000));
    EXPECT_CALL(*mock_surface->xdg_surface(),
                AckConfigure(Eq(kConfigureSerial)));
    // No new xdg_surface.set_window_geometry requests as DIP geometry
    // has not changed.
    EXPECT_CALL(*mock_surface->xdg_surface(), SetWindowGeometry(_)).Times(0);
  });
  SendConfigureEvent(new_window_surface_id, gfx::Size(0, 0),
                     wl::ScopedWlArray({XDG_TOPLEVEL_STATE_ACTIVATED}),
                     kConfigureSerial);
  CreateBufferAndPresentAsNewFrame(new_window.get(), new_window_delegate,
                                   /*buffer_size=*/gfx::Size(2000, 2000),
                                   /*buffer_scale=*/2.5f);

  // Sync display and verify the expectations.
  VerifyAndClearExpectations(new_window_delegate, new_window_surface_id);
  EXPECT_EQ(new_window->applied_state(), new_window->latched_state());
  EXPECT_EQ(new_window->latched_state().ui_scale, 1.25f);
  EXPECT_EQ(new_window->latched_state().window_scale, 2.0f);
  EXPECT_EQ(new_window->root_surface()->state_.buffer_scale_float, 2.0f);
}

TEST_P(WaylandWindowTest, UiScale_HandlePopupGeometry) {
  ASSERT_TRUE(connection_->IsUiScaleEnabled());

  // Required for emulating mouse events.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    wl_seat_send_capabilities(server->seat()->resource(),
                              WL_SEAT_CAPABILITY_POINTER);
  });
  ASSERT_TRUE(connection_->seat()->pointer());

  // Set font scale to 1.25.
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
  connection_->window_manager()->SetFontScale(1.25f);
  Mock::VerifyAndClearExpectations(&delegate_);
  WaylandTestBase::SyncDisplay();
  EXPECT_EQ(1.25f, window_->applied_state().ui_scale);
  EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  EXPECT_EQ(gfx::Size(640, 480), window_->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(800, 600), window_->applied_state().size_px);

  // Initialize create and show a 20x80 popup at (100, 100) ui size/location
  // (1.25 ui inv-scaled). So it must be positioned at (125, 125) with 25x100
  // dip wayland pixels.
  auto* toplevel = window_.get();
  testing::NiceMock<MockWaylandPlatformWindowDelegate> menu_delegate(
      connection_.get());
  ui::OwnedWindowAnchor anchor{
      .anchor_rect = gfx::Rect(100, 100, 20, 20),
      .anchor_position = OwnedWindowAnchorPosition::kBottomRight,
      .anchor_gravity = OwnedWindowAnchorGravity::kBottomLeft};
  EXPECT_CALL(menu_delegate, GetOwnedWindowAnchorAndRectInDIP())
      .WillOnce(Return(anchor));
  auto menu_window = CreateWaylandWindowWithParams(
      PlatformWindowType::kMenu, gfx::Rect(100, 100, 20, 80), &menu_delegate,
      toplevel->GetWidget());
  ASSERT_TRUE(menu_window);
  const uint32_t menu_surface_id =
      menu_window->root_surface()->get_surface_id();
  PostToServerAndWait([menu_surface_id](wl::TestWaylandServerThread* server) {
    auto* popup = GetTestXdgPopupByWindow(server, menu_surface_id);
    ASSERT_TRUE(popup);
    EXPECT_EQ(popup->anchor_rect(), gfx::Rect(125, 125, 25, 25));
    EXPECT_EQ(popup->size(), gfx::Size(25, 100));
  });
  PlatformWindowDelegate::State initial_state(menu_window->applied_state());

  // Emulate a popup configure with empty geometry rectangle, in which case the
  // current popup bounds is expected to be used.
  SendConfigureEvent(menu_surface_id, gfx::Size(0, 0), wl::ScopedWlArray({}));
  EXPECT_EQ(initial_state.ToString(), menu_window->applied_state().ToString());

  // Now emulate a popup configure with a 100x200 geometry, and verifies that:
  // 1. A state change request is triggered with the ui_scale'd equivalent dip
  // geometry 80x160.
  // 2. The expected wayland requests (along with the correct geometry and scale
  // related parameters) are issued once the new frame is received from Viz.
  const uint32_t kLatestConfigureSerial = 11u;
  PostToServerAndWait([menu_surface_id](wl::TestWaylandServerThread* server) {
    wl::MockSurface* mock_surface =
        server->GetObject<wl::MockSurface>(menu_surface_id);
    ASSERT_TRUE(mock_surface->xdg_surface());
    EXPECT_CALL(*mock_surface, Damage(0, 0, 100, 200));
    EXPECT_CALL(*mock_surface->xdg_surface(),
                SetWindowGeometry(gfx::Rect(100, 200)));
    EXPECT_CALL(*mock_surface->xdg_surface(),
                AckConfigure(Eq(kLatestConfigureSerial)));
  });
  // Configure popup window to origin 100,100 and size 100x200.
  PostToServerAndWait([id = menu_surface_id](
                          wl::TestWaylandServerThread* server) {
    auto* surface = server->GetObject<wl::MockSurface>(id);
    ASSERT_TRUE(surface);
    auto* xdg_surface = surface->xdg_surface();
    ASSERT_TRUE(xdg_surface);
    ASSERT_TRUE(xdg_surface->xdg_popup()->resource());
    xdg_popup_send_configure(xdg_surface->xdg_popup()->resource(), 100, 100,
                             100, 200);
    xdg_surface_send_configure(xdg_surface->resource(), kLatestConfigureSerial);
  });

  EXPECT_EQ(menu_window->applied_state().bounds_dip,
            gfx::Rect(80, 80, 80, 160));
  CreateBufferAndPresentAsNewFrame(menu_window.get(), menu_delegate,
                                   /*buffer_size=*/gfx::Size(100, 200),
                                   /*buffer_scale=*/1.25f);
  // Sync display and verify the expectations.
  VerifyAndClearExpectations(menu_delegate, menu_surface_id);
  EXPECT_EQ(menu_window->applied_state(), menu_window->latched_state());
  EXPECT_EQ(menu_window->latched_state().ui_scale, 1.25f);
  EXPECT_EQ(menu_window->latched_state().window_scale, 1.0f);
  EXPECT_EQ(menu_window->root_surface()->state_.buffer_scale_float, 1.0f);

  // Verifies both event dispatching and screen "cursor location" API works` as
  // expected with ui_scale > 1. Regression test fo https://crbug.com/396457560.
  std::unique_ptr<Event> event;
  EXPECT_CALL(menu_delegate, DispatchEvent(_))
      .WillRepeatedly(CloneEvent(&event));
  PostToServerAndWait(
      [id = menu_surface_id](wl::TestWaylandServerThread* server) {
        auto* pointer_resource = server->seat()->pointer()->resource();
        auto* menu_surface = server->GetObject<wl::MockSurface>(id);
        wl_pointer_send_enter(pointer_resource, server->GetNextSerial(),
                              menu_surface->resource(), 0, 0);
        wl_pointer_send_motion(pointer_resource, server->GetNextSerial(),
                               wl_fixed_from_double(10.0f),
                               wl_fixed_from_double(10.0f));
      });
  ASSERT_TRUE(event);
  ASSERT_TRUE(event->IsMouseEvent());
  ASSERT_EQ(event->type(), ui::EventType::kMouseMoved);
  // Event dispatching API expects pixel coordinates.
  EXPECT_EQ(event->AsLocatedEvent()->location(), gfx::Point(10, 10));
  EXPECT_EQ(event->AsLocatedEvent()->root_location(), gfx::Point(10, 10));
  // Internal cursor position tracker uses Wayland DIP coordinates.
  ASSERT_TRUE(connection_->wayland_cursor_position());
  EXPECT_EQ(connection_->wayland_cursor_position()->GetCursorSurfacePoint(),
            gfx::Point(110, 110));
  // Screen API uses UI DIP coordinates.
  EXPECT_EQ(screen_->GetCursorScreenPoint(), gfx::Point(88, 88));
}

TEST_P(WaylandWindowTest, UiScale_SanitizeFontScale) {
  ASSERT_TRUE(connection_->IsUiScaleEnabled());

  auto test_font_scale = [&](float requested_font_scale,
                             float sanitized_font_scale) {
    EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
    connection_->window_manager()->SetFontScale(requested_font_scale);
    Mock::VerifyAndClearExpectations(&delegate_);
    WaylandTestBase::SyncDisplay();
    EXPECT_EQ(sanitized_font_scale, window_->applied_state().ui_scale);
    EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  };

  // Set arbitrary font scale values and verify expectations.
  test_font_scale(20.0f, 3.0f);
  test_font_scale(0.10f, 0.5f);
  test_font_scale(1.5f, 1.5f);
  test_font_scale(-1.0f, 0.5f);
}

TEST_P(WaylandWindowTest, UiScale_ForceDeviceScaleFactor) {
  // When enabled, it must take precedence over font scale.
  ASSERT_TRUE(connection_->IsUiScaleEnabled());
  display::Display::SetForceDeviceScaleFactor(2.0);
  EXPECT_EQ(2.0f, connection_->window_manager()->DetermineUiScale());
  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange))).Times(1);
  SendConfigureEvent(surface_id_, gfx::Size(1000, 1000), wl::ScopedWlArray({}));
  VerifyAndClearExpectations();
  // Ensure the forced scale factor takes precedence (over font scale) and
  // gets used as ui scale when firing the bounds change triggered by the
  // server, in this case.
  EXPECT_EQ(2.0f, window_->applied_state().ui_scale);
  EXPECT_EQ(1.0f, window_->applied_state().window_scale);
  EXPECT_EQ(gfx::Size(500, 500), window_->applied_state().bounds_dip.size());
  EXPECT_EQ(gfx::Size(1000, 1000), window_->applied_state().size_px);
  PlatformWindowDelegate::State previous_state(window_->applied_state());

  // Font scale changes should not trigger bounds change when
  // force-device-scale-factor is in use.
  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
  connection_->window_manager()->SetFontScale(1.25f);
  Mock::VerifyAndClearExpectations(&delegate_);
  EXPECT_EQ(2.0f, connection_->window_manager()->DetermineUiScale());
  EXPECT_EQ(window_->applied_state(), previous_state);
}

// Regression POC: WaylandToplevelWindow::HandleToplevelConfigure() continues to
// use `this` after delegate()->OnActivationChanged() synchronously destroys the
// platform window. This mirrors the production path documented at
// DesktopWindowTreeHostPlatform::OnActivationChanged where
// HandleActivationChanged() can synchronously close the widget, which in turn
// calls SetPlatformWindow(nullptr) and frees the WaylandToplevelWindow while
// the xdg_toplevel.configure handler is still on the stack.
TEST_P(WaylandWindowTest, HandleToplevelConfigureSyncCloseOnDeactivate) {
  // After SetUp(), |window_| has already received an activated configure, so
  // is_xdg_active_ == is_active_ == true.
  ASSERT_TRUE(window_);
  WaylandWindow* raw_window = window_.get();

  // Simulate a delegate that destroys the platform window inside
  // OnActivationChanged(false) — exactly what happens in production when a
  // WidgetObserver calls Widget::CloseNow() on deactivation, leading to
  // DesktopWindowTreeHostPlatform::OnClosed -> SetPlatformWindow(nullptr).
  EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)))
      .WillOnce(InvokeWithoutArgs([this]() { window_.reset(); }));

  // Don't try to talk to the server after the window has been torn down
  // mid-dispatch.
  DisableSyncOnTearDown();

  // Drive the standard xdg_toplevel.configure entry point with the activated
  // bit cleared. This calls HandleToplevelConfigureWithOrigin() ->
  // UpdateActivationState() -> delegate()->OnActivationChanged(false), which
  // (via the mock above) frees `this`. Control then returns to
  // HandleToplevelConfigure:469 which calls UpdateSessionStateIfNeeded() on
  // the freed object.
  WaylandWindow::WindowStates deactivated_states;
  deactivated_states.is_activated = false;
  raw_window->HandleToplevelConfigure(800, 600, deactivated_states);

  // If we got here without ASAN reporting a heap-use-after-free, the bug is
  // fixed.
  EXPECT_FALSE(window_);
}

TEST_P(WaylandWindowTest, WaylandPopupSetBoundsUaf) {
  MockWaylandPlatformWindowDelegate popup_delegate(connection_.get());
  gfx::Rect popup_bounds(10, 10, 50, 50);
  auto wayland_popup =
      CreateWaylandWindowWithParams(PlatformWindowType::kPopup, popup_bounds,
                                    &popup_delegate, window_->GetWidget());
  ASSERT_TRUE(wayland_popup);

  popup_delegate.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
    wayland_popup.reset();
    return true;
  }));

  // This should not crash if the fix is applied.
  wayland_popup->SetBoundsInDIP(gfx::Rect(15, 15, 60, 60));
}

TEST_P(WaylandWindowTest, WaylandBubbleSetBoundsUaf) {
  MockWaylandPlatformWindowDelegate bubble_delegate(connection_.get());
  gfx::Rect bubble_bounds(10, 10, 50, 50);
  auto wayland_bubble =
      CreateWaylandWindowWithParams(PlatformWindowType::kBubble, bubble_bounds,
                                    &bubble_delegate, window_->GetWidget());
  ASSERT_TRUE(wayland_bubble);

  bubble_delegate.set_on_state_update_callback(
      base::BindLambdaForTesting([&]() {
        wayland_bubble.reset();
        return true;
      }));

  // This should not crash if the fix is applied.
  wayland_bubble->SetBoundsInDIP(gfx::Rect(15, 15, 60, 60));
}

TEST_P(WaylandWindowTest, WaylandBubbleActivateBubbleUaf) {
  MockWaylandPlatformWindowDelegate bubble_delegate(connection_.get());
  gfx::Rect bubble_bounds(10, 10, 50, 50);
  auto wayland_bubble =
      CreateWaylandWindowWithParams(PlatformWindowType::kBubble, bubble_bounds,
                                    &bubble_delegate, window_->GetWidget());
  ASSERT_TRUE(wayland_bubble);

  EXPECT_CALL(delegate_, OnActivationChanged(::testing::_))
      .Times(::testing::AnyNumber());

  EXPECT_CALL(delegate_, OnActivationChanged(false))
      .WillOnce(::testing::InvokeWithoutArgs([&]() {
        wayland_bubble.reset();
        window_.reset();
      }));

  // This should not crash and should return safely.
  window_->ActivateBubble(wayland_bubble->AsWaylandBubble());
}

TEST_P(WaylandWindowTest, WaylandBubbleRemoveBubbleUaf) {
  auto active = MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED});
  SendConfigureEvent(surface_id_, {0, 0}, active);
  AdvanceFrameToCurrent(window_.get(), delegate_);
  VerifyAndClearExpectations();

  MockWaylandPlatformWindowDelegate bubble_delegate(connection_.get());
  gfx::Rect bubble_bounds(10, 10, 50, 50);
  auto wayland_bubble =
      CreateWaylandWindowWithParams(PlatformWindowType::kBubble, bubble_bounds,
                                    &bubble_delegate, window_->GetWidget());
  ASSERT_TRUE(wayland_bubble);

  window_->ActivateBubble(wayland_bubble->AsWaylandBubble());

  EXPECT_CALL(delegate_, OnActivationChanged(::testing::_))
      .Times(::testing::AnyNumber());

  EXPECT_CALL(delegate_, OnActivationChanged(true))
      .WillOnce(::testing::InvokeWithoutArgs([&]() {
        wayland_bubble.reset();
        window_.reset();
      }));

  // This should not crash and should return safely.
  window_->RemoveBubble(wayland_bubble->AsWaylandBubble());
}

TEST_P(WaylandWindowTest, WaylandBubbleUpdateWindowScaleUaf) {
  MockWaylandPlatformWindowDelegate bubble_delegate(connection_.get());
  gfx::Rect bubble_bounds(10, 10, 50, 50);
  auto wayland_bubble =
      CreateWaylandWindowWithParams(PlatformWindowType::kBubble, bubble_bounds,
                                    &bubble_delegate, window_->GetWidget());
  ASSERT_TRUE(wayland_bubble);

  bubble_delegate.set_on_state_update_callback(
      base::BindLambdaForTesting([&]() {
        wayland_bubble.reset();
        return true;
      }));

  // This should not crash.
  wayland_bubble->UpdateWindowScale(true);
}

TEST_P(WaylandWindowTest, WaylandBubbleShowUaf) {
  MockWaylandPlatformWindowDelegate bubble_delegate(connection_.get());
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(10, 10, 50, 50);
  properties.type = PlatformWindowType::kBubble;
  properties.parent_widget = window_->GetWidget();
  auto wayland_bubble = bubble_delegate.CreateWaylandWindow(
      connection_.get(), std::move(properties));
  ASSERT_TRUE(wayland_bubble);

  bubble_delegate.set_on_state_update_callback(
      base::BindLambdaForTesting([&]() {
        wayland_bubble.reset();
        return true;
      }));

  // This should not crash.
  wayland_bubble->Show(false);
}

TEST_P(WaylandWindowTest, WaylandPopupShowUaf) {
  MockWaylandPlatformWindowDelegate popup_delegate(connection_.get());
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(10, 10, 50, 50);
  properties.type = PlatformWindowType::kPopup;
  properties.parent_widget = window_->GetWidget();
  auto wayland_popup = popup_delegate.CreateWaylandWindow(
      connection_.get(), std::move(properties));
  ASSERT_TRUE(wayland_popup);

  popup_delegate.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
    wayland_popup.reset();
    return true;
  }));

  // This should not crash.
  wayland_popup->Show(false);
}

TEST_P(WaylandWindowTest, WaylandToplevelWindowOnPaintAsActiveChangedUaf) {
  testing::NiceMock<MockWaylandPlatformWindowDelegate> toplevel_delegate(
      connection_.get());
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(10, 10, 100, 100);
  properties.type = PlatformWindowType::kWindow;
  auto toplevel_window = toplevel_delegate.CreateWaylandWindow(
      connection_.get(), std::move(properties));
  ASSERT_TRUE(toplevel_window);

  EXPECT_CALL(toplevel_delegate, OnPaintAsActiveChanged(::testing::_))
      .WillOnce(
          ::testing::InvokeWithoutArgs([&]() { toplevel_window.reset(); }));

  WaylandWindow::WindowStates window_states;
  window_states.is_activated = true;
  // This should not crash.
  toplevel_window->HandleToplevelConfigure(100, 100, window_states);
}

TEST_P(WaylandWindowTest, WaylandToplevelWindowTriggerStateChangesUaf) {
  testing::NiceMock<MockWaylandPlatformWindowDelegate> toplevel_delegate(
      connection_.get());
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(10, 10, 100, 100);
  properties.type = PlatformWindowType::kWindow;
  auto toplevel_window = toplevel_delegate.CreateWaylandWindow(
      connection_.get(), std::move(properties));
  ASSERT_TRUE(toplevel_window);

  EXPECT_CALL(toplevel_delegate,
              OnWindowStateChanged(::testing::_, ::testing::_))
      .WillOnce(
          ::testing::InvokeWithoutArgs([&]() { toplevel_window.reset(); }));

  // This should not crash.
  toplevel_window->Maximize();
}

INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                         WaylandWindowTest,
                         Values(wl::ServerConfig{}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                         WaylandWindowTestNoFractionalScale,
                         Values(wl::ServerConfig{
                             .supports_viewporter_surface_scaling = false}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                         MultiDisplayWaylandWindowTest,
                         Values(wl::ServerConfig{}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
                         WaylandSubsurfaceTest,
                         Values(wl::ServerConfig{}));

}  // namespace ui
