chromium/ash/frame_throttler/frame_throttling_controller_unittest.cc

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

#include "ash/frame_throttler/frame_throttling_controller.h"

#include <utility>
#include <vector>

#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "base/functional/callback.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/test/test_frame_sink_manager.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/viz/privileged/mojom/compositing/frame_sink_manager.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/rect.h"

namespace ash {
namespace {

using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;

class FakeFrameSinkManagerImpl : public viz::TestFrameSinkManagerImpl {
 public:
  FakeFrameSinkManagerImpl() = default;
  FakeFrameSinkManagerImpl(const FakeFrameSinkManagerImpl&) = delete;
  FakeFrameSinkManagerImpl& operator=(const FakeFrameSinkManagerImpl&) = delete;
  ~FakeFrameSinkManagerImpl() override = default;

  // mojom::FrameSinkManager implementation:
  void Throttle(const std::vector<viz::FrameSinkId>& ids,
                base::TimeDelta interval) override {
    throttled_interval_ = interval;
    throttled_frame_sink_ids_ = ids;
  }

  base::TimeDelta throttled_interval() const { return throttled_interval_; }
  const std::vector<viz::FrameSinkId>& throttled_frame_sink_ids() const {
    return throttled_frame_sink_ids_;
  }

 private:
  base::TimeDelta throttled_interval_;
  std::vector<viz::FrameSinkId> throttled_frame_sink_ids_;
};

class FrameThrottlingControllerTest : public AshTestBase {
 protected:
  FrameThrottlingControllerTest() : controller_(&host_frame_sink_manager_) {
    mojo::PendingRemote<viz::mojom::FrameSinkManager> frame_sink_manager;
    mojo::PendingReceiver<viz::mojom::FrameSinkManager>
        frame_sink_manager_receiver =
            frame_sink_manager.InitWithNewPipeAndPassReceiver();
    mojo::PendingRemote<viz::mojom::FrameSinkManagerClient>
        frame_sink_manager_client;
    mojo::PendingReceiver<viz::mojom::FrameSinkManagerClient>
        frame_sink_manager_client_receiver =
            frame_sink_manager_client.InitWithNewPipeAndPassReceiver();

    host_frame_sink_manager_.BindAndSetManager(
        std::move(frame_sink_manager_client_receiver),
        task_environment()->GetMainThreadTaskRunner(),
        std::move(frame_sink_manager));
    frame_sink_manager_impl_.BindReceiver(
        std::move(frame_sink_manager_receiver),
        std::move(frame_sink_manager_client));
  }

  void SetUp() override {
    AshTestBase::SetUp();
    controller_.OnWindowTreeHostCreated(ash_test_helper()->GetHost());
  }

  std::unique_ptr<aura::Window> CreateTestBrowserWindow(
      const viz::FrameSinkId frame_sink_id) {
    std::unique_ptr<aura::Window> browser_window =
        CreateAppWindow(gfx::Rect(100, 100), chromeos::AppType::BROWSER);
    browser_window->SetEmbedFrameSinkId(frame_sink_id);
    return browser_window;
  }

  FakeFrameSinkManagerImpl frame_sink_manager_impl_;
  viz::HostFrameSinkManager host_frame_sink_manager_;
  FrameThrottlingController controller_;
};

TEST_F(FrameThrottlingControllerTest, ManualThrottling) {
  std::unique_ptr<aura::Window> window_1 = CreateTestWindow();
  window_1->SetEmbedFrameSinkId({1, 1});
  std::unique_ptr<aura::Window> window_2 = CreateTestWindow();
  window_2->SetEmbedFrameSinkId({2, 2});

  // 10 Hz is lower than the default, but there are no other windows actively
  // being throttled, so 10 Hz should be used.
  controller_.StartThrottling({window_1.get(), window_2.get()},
                              base::Hertz(10));
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(frame_sink_manager_impl_.throttled_interval(),
              Eq(base::Hertz(10)));
  EXPECT_THAT(
      frame_sink_manager_impl_.throttled_frame_sink_ids(),
      UnorderedElementsAre(viz::FrameSinkId({1, 1}), viz::FrameSinkId({2, 2})));

  controller_.StartThrottling({window_1.get()});
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(frame_sink_manager_impl_.throttled_interval(),
              Eq(base::Hertz(kDefaultThrottleFps)));
  EXPECT_THAT(frame_sink_manager_impl_.throttled_frame_sink_ids(),
              UnorderedElementsAre(viz::FrameSinkId({1, 1})));

  controller_.EndThrottling();
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(frame_sink_manager_impl_.throttled_frame_sink_ids(), IsEmpty());
}

TEST_F(FrameThrottlingControllerTest, CompositingBasedThrottling) {
  constexpr viz::FrameSinkId kBrowserWindowFrameSinkId = {99, 99};

  std::unique_ptr<aura::Window> browser_window =
      CreateTestBrowserWindow(kBrowserWindowFrameSinkId);

  controller_.OnCompositingFrameSinksToThrottleUpdated(
      ash_test_helper()->GetHost(), {kBrowserWindowFrameSinkId});
  base::RunLoop().RunUntilIdle();

  EXPECT_THAT(frame_sink_manager_impl_.throttled_interval(),
              Eq(base::Hertz(kDefaultThrottleFps)));
  EXPECT_THAT(frame_sink_manager_impl_.throttled_frame_sink_ids(),
              UnorderedElementsAre(kBrowserWindowFrameSinkId));
}

TEST_F(FrameThrottlingControllerTest, ManualAndCompositingBasedThrottling) {
  constexpr viz::FrameSinkId kBrowserWindowFrameSinkId = {99, 99};
  constexpr viz::FrameSinkId kManualWindowFrameSinkId = {100, 100};

  std::unique_ptr<aura::Window> browser_window =
      CreateTestBrowserWindow(kBrowserWindowFrameSinkId);

  controller_.OnCompositingFrameSinksToThrottleUpdated(
      ash_test_helper()->GetHost(), {kBrowserWindowFrameSinkId});
  base::RunLoop().RunUntilIdle();

  std::unique_ptr<aura::Window> manual_window = CreateTestWindow();
  manual_window->SetEmbedFrameSinkId(kManualWindowFrameSinkId);

  // 10 Hz is lower than the default frame rate, so it should be rejected.
  controller_.StartThrottling({manual_window.get()}, base::Hertz(10));
  base::RunLoop().RunUntilIdle();

  EXPECT_THAT(frame_sink_manager_impl_.throttled_interval(),
              Eq(base::Hertz(kDefaultThrottleFps)));
  EXPECT_THAT(frame_sink_manager_impl_.throttled_frame_sink_ids(),
              UnorderedElementsAre(kBrowserWindowFrameSinkId,
                                   kManualWindowFrameSinkId));

  // 30 Hz is higher than the default frame rate, so it should be respected.
  controller_.StartThrottling({manual_window.get()}, base::Hertz(30));
  base::RunLoop().RunUntilIdle();

  EXPECT_THAT(frame_sink_manager_impl_.throttled_interval(),
              Eq(base::Hertz(30)));
  EXPECT_THAT(frame_sink_manager_impl_.throttled_frame_sink_ids(),
              UnorderedElementsAre(kBrowserWindowFrameSinkId,
                                   kManualWindowFrameSinkId));

  // The frame rate should return to default after manual window is removed.
  controller_.EndThrottling();
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(frame_sink_manager_impl_.throttled_interval(),
              Eq(base::Hertz(kDefaultThrottleFps)));
  EXPECT_THAT(frame_sink_manager_impl_.throttled_frame_sink_ids(),
              UnorderedElementsAre(kBrowserWindowFrameSinkId));
}

}  // namespace
}  // namespace ash