chromium/components/exo/wayland/output_controller_unittest.cc

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

#include "components/exo/wayland/output_controller.h"

#include <wayland-server-core.h>

#include "components/exo/wayland/output_controller_test_api.h"
#include "components/exo/wayland/test/wayland_server_test.h"
#include "components/exo/wayland/wayland_display_output.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/wayland/wayland_display_util.h"

namespace exo::wayland {

namespace {

class MockOutputControllerDelegate : public OutputController::Delegate {
 public:
  MockOutputControllerDelegate() : wayland_display_(wl_display_create()) {}
  ~MockOutputControllerDelegate() override {
    wl_display_destroy(wayland_display_.ExtractAsDangling());
  }

  // OutputController::Delegate:
  wl_display* GetWaylandDisplay() override { return wayland_display_; }
  MOCK_METHOD(void, Flush, (), (override));

 private:
  raw_ptr<wl_display> wayland_display_;
};

}  // namespace

class OutputControllerTest : public test::WaylandServerTest {
 public:
  OutputControllerTest() = default;
  OutputControllerTest(const OutputControllerTest&) = delete;
  OutputControllerTest& operator=(const OutputControllerTest&) = delete;
  ~OutputControllerTest() override = default;

 protected:
  MockOutputControllerDelegate delegate_;
};

TEST_F(OutputControllerTest, OutputControllerInitialization) {
  UpdateDisplay("800x600,800x600");
  const auto* screen = display::Screen::GetScreen();
  const int64_t primary_id = screen->GetAllDisplays()[0].id();
  const int64_t secondary_id = screen->GetAllDisplays()[1].id();
  ASSERT_EQ(2u, screen->GetAllDisplays().size());

  // OutputController should reflect the display state in display manager after
  // initialization.
  EXPECT_CALL(delegate_, Flush()).Times(1);
  OutputController output_controller(&delegate_);
  OutputControllerTestApi output_controller_test_api(output_controller);
  EXPECT_EQ(2u, output_controller_test_api.GetOutputMap().size());
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(secondary_id));
}

TEST_F(OutputControllerTest, OutputControllerRemoveDisplay) {
  UpdateDisplay("800x600,800x600");
  const auto* screen = display::Screen::GetScreen();
  const int64_t primary_id = screen->GetAllDisplays()[0].id();
  const int64_t secondary_id = screen->GetAllDisplays()[1].id();
  ASSERT_EQ(2u, screen->GetAllDisplays().size());

  EXPECT_CALL(delegate_, Flush()).Times(2);
  OutputController output_controller(&delegate_);
  OutputControllerTestApi output_controller_test_api(output_controller);
  EXPECT_EQ(2u, output_controller_test_api.GetOutputMap().size());
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(secondary_id));

  // Remove the secondary display.
  UpdateDisplay("800x600");
  EXPECT_EQ(1u, output_controller_test_api.GetOutputMap().size());
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
}

TEST_F(OutputControllerTest, OutputControllerAddDisplay) {
  UpdateDisplay("800x600");
  const auto* screen = display::Screen::GetScreen();
  const int64_t primary_id = screen->GetAllDisplays()[0].id();
  ASSERT_EQ(1u, screen->GetAllDisplays().size());

  EXPECT_CALL(delegate_, Flush()).Times(2);
  OutputController output_controller(&delegate_);
  OutputControllerTestApi output_controller_test_api(output_controller);
  EXPECT_EQ(1u, output_controller_test_api.GetOutputMap().size());
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));

  // Add a second display.
  UpdateDisplay("800x600,800x600");
  const int64_t secondary_id = screen->GetAllDisplays()[1].id();
  ASSERT_EQ(2u, screen->GetAllDisplays().size());
  EXPECT_EQ(2u, output_controller_test_api.GetOutputMap().size());
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
  EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(secondary_id));
}

// Regression test for b/323403137. Tests that exo will respect display
// activation events in the case observers trigger activation updates before the
// controller has had the opportunity to update output state.
TEST_F(OutputControllerTest, ActiveDisplay) {
  // Activates a second display when added to the system.
  class SecondaryDisplayActivator : public display::DisplayManagerObserver {
   public:
    SecondaryDisplayActivator() {
      display_manager_observation_.Observe(
          ash::Shell::Get()->display_manager());
    }
    // display::DisplayManagerObserver:
    void OnDidProcessDisplayChanges(
        const DisplayConfigurationChange& configuration_change) override {
      auto* screen = display::Screen::GetScreen();
      EXPECT_EQ(2u, screen->GetAllDisplays().size());
      screen->SetDisplayForNewWindows(screen->GetAllDisplays()[1].id());
    }

   private:
    base::ScopedObservation<display::DisplayManager,
                            display::DisplayManagerObserver>
        display_manager_observation_{this};
  };

  // Setup the environment with a single display.
  UpdateDisplay("800x600");
  auto* screen = display::Screen::GetScreen();
  const int64_t primary_id = screen->GetAllDisplays()[0].id();
  ASSERT_EQ(1u, screen->GetAllDisplays().size());
  OutputController output_controller(&delegate_);
  OutputControllerTestApi output_controller_test_api(output_controller);

  // Force an activation notification for the primary display.
  screen->SetDisplayForNewWindows(primary_id);
  EXPECT_EQ(primary_id,
            output_controller_test_api.GetDispatchedActivatedDisplayId());

  // Update the display manager observer ordering such that the display
  // activator is notified before the output controller.
  SecondaryDisplayActivator secondary_display_activator;
  output_controller_test_api.ResetDisplayManagerObservation();

  // Add a secondary display. The display activator will send a display
  // activation notification before the controller processes the display update.
  // Make sure the activation event is preserved.
  UpdateDisplay("800x600,800x600");
  ASSERT_EQ(2u, screen->GetAllDisplays().size());
  const int64_t secondary_id = screen->GetAllDisplays()[1].id();
  EXPECT_EQ(secondary_id,
            output_controller_test_api.GetDispatchedActivatedDisplayId());
}

}  // namespace exo::wayland