chromium/chrome/browser/ash/crosapi/screen_manager_ash_browsertest.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/crosapi/screen_manager_ash.h"

#include <memory>

#include "ash/public/cpp/test/shell_test_api.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/test/test_future.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "content/public/test/browser_test.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "ui/aura/window.h"
#include "ui/display/test/display_manager_test_api.h"

namespace crosapi {
namespace {

// This class tests that the ash-chrome implementation of the screen manager
// crosapi works properly.
class ScreenManagerAshBrowserTest : public InProcessBrowserTest {
 protected:
  using SMRemote = mojo::Remote<mojom::ScreenManager>;
  using SMPendingRemote = mojo::PendingRemote<mojom::ScreenManager>;
  using SMPendingReceiver = mojo::PendingReceiver<mojom::ScreenManager>;

  ScreenManagerAshBrowserTest() = default;

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

  ~ScreenManagerAshBrowserTest() override {
    background_sequence_->DeleteSoon(FROM_HERE,
                                     std::move(screen_manager_remote_));
  }

  void SetUpOnMainThread() override {
    // The implementation of screen manager is affine to this sequence.
    screen_manager_ = std::make_unique<ScreenManagerAsh>();

    SMPendingRemote pending_remote;
    SMPendingReceiver pending_receiver =
        pending_remote.InitWithNewPipeAndPassReceiver();

    // Bind the implementation of ScreenManager to this sequence.
    screen_manager_->BindReceiver(std::move(pending_receiver));

    // Bind the remote to a background sequence. This is necessary because the
    // screen manager API is synchronous and blocks the calling sequence.
    background_sequence_ = base::ThreadPool::CreateSequencedTaskRunner(
        {base::TaskPriority::USER_BLOCKING, base::MayBlock(),
         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});

    // We construct the remote on this sequence for simplicity. All subsequent
    // invocations, including destruction, are from the background sequence.
    screen_manager_remote_ = std::make_unique<SMRemote>();
    auto bind_background = base::BindOnce(
        [](SMRemote* remote, SMPendingRemote pending_remote) {
          remote->Bind(std::move(pending_remote));
        },
        screen_manager_remote_.get(), std::move(pending_remote));
    background_sequence_->PostTask(FROM_HERE, std::move(bind_background));
  }

  void PostTaskAndWait(base::OnceClosure closure) {
    base::test::TestFuture<void> future;
    background_sequence_->PostTaskAndReply(FROM_HERE, std::move(closure),
                                           future.GetCallback());
    EXPECT_TRUE(future.Wait());
  }

  // Affine to main sequence.
  std::unique_ptr<ScreenManagerAsh> screen_manager_;

  // A sequence that is allowed to block.
  scoped_refptr<base::SequencedTaskRunner> background_sequence_;

  // Affine to background sequence.
  std::unique_ptr<SMRemote> screen_manager_remote_;
};

IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, ScreenCapturer) {
  bool success;
  SkBitmap snapshot;

  // Take a snapshot on a background sequence. The call is blocking, so when it
  // finishes, we can also unblock the main thread.
  auto take_snapshot_background = base::BindOnce(
      [](SMRemote* remote, bool* success, SkBitmap* snapshot) {
        mojo::Remote<mojom::SnapshotCapturer> capturer;
        (*remote)->GetScreenCapturer(capturer.BindNewPipeAndPassReceiver());

        {
          mojo::ScopedAllowSyncCallForTesting allow_sync;

          std::vector<mojom::SnapshotSourcePtr> screens;
          capturer->ListSources(&screens);

          // There should be at least one screen!
          ASSERT_LE(1u, screens.size());

          capturer->TakeSnapshot(screens[0]->id, success, snapshot);
        }
      },
      screen_manager_remote_.get(), &success, &snapshot);
  PostTaskAndWait(std::move(take_snapshot_background));

  // Check that the IPC succeeded.
  ASSERT_TRUE(success);

  // Check that the screenshot has the right dimensions.
  aura::Window* primary_window =
      browser()->window()->GetNativeWindow()->GetRootWindow();
  EXPECT_EQ(int{snapshot.width()}, primary_window->bounds().width());
  EXPECT_EQ(int{snapshot.height()}, primary_window->bounds().height());
}

IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest,
                       ScreenCapturer_MultipleDisplays) {
  bool success[2];
  SkBitmap snapshot[2];

  display::test::DisplayManagerTestApi(ash::ShellTestApi().display_manager())
      .UpdateDisplay("400x300,100x500");

  // Take a snapshot on a background sequence. The call is blocking, so when it
  // finishes, we can also unblock the main thread.
  auto take_snapshot_background = base::BindOnce(
      [](SMRemote* remote, bool success[2], SkBitmap snapshot[2]) {
        mojo::Remote<mojom::SnapshotCapturer> capturer;
        (*remote)->GetScreenCapturer(capturer.BindNewPipeAndPassReceiver());

        {
          mojo::ScopedAllowSyncCallForTesting allow_sync;

          std::vector<mojom::SnapshotSourcePtr> screens;
          capturer->ListSources(&screens);

          // There should be exactly two screens!
          ASSERT_EQ(2u, screens.size());

          capturer->TakeSnapshot(screens[0]->id, &success[0], &snapshot[0]);
          capturer->TakeSnapshot(screens[1]->id, &success[1], &snapshot[1]);
        }
      },
      screen_manager_remote_.get(), success, snapshot);
  PostTaskAndWait(std::move(take_snapshot_background));

  // Check that the IPCs succeeded.
  ASSERT_TRUE(success[0]);
  ASSERT_TRUE(success[1]);

  // Check that the screenshots have the right dimensions.
  EXPECT_EQ(400, int{snapshot[0].width()});
  EXPECT_EQ(300, int{snapshot[0].height()});
  EXPECT_EQ(100, int{snapshot[1].width()});
  EXPECT_EQ(500, int{snapshot[1].height()});
}

IN_PROC_BROWSER_TEST_F(ScreenManagerAshBrowserTest, WindowCapturer) {
  bool success;
  SkBitmap snapshot;

  // Take a snapshot on a background sequence. The call is blocking, so when it
  // finishes, we can also unblock the main thread.
  auto take_snapshot_background = base::BindOnce(
      [](SMRemote* remote, bool* success, SkBitmap* snapshot) {
        mojo::Remote<mojom::SnapshotCapturer> capturer;
        (*remote)->GetWindowCapturer(capturer.BindNewPipeAndPassReceiver());

        {
          mojo::ScopedAllowSyncCallForTesting allow_sync;

          std::vector<mojom::SnapshotSourcePtr> windows;
          capturer->ListSources(&windows);

          // There should be at least one window!
          ASSERT_LE(1u, windows.size());

          capturer->TakeSnapshot(windows[0]->id, success, snapshot);
        }
      },
      screen_manager_remote_.get(), &success, &snapshot);
  PostTaskAndWait(std::move(take_snapshot_background));

  // Check that the IPC succeeded.
  ASSERT_TRUE(success);

  // Check that the screenshot has the right dimensions.
  aura::Window* window = browser()->window()->GetNativeWindow();
  EXPECT_EQ(int{snapshot.width()}, window->bounds().width());
  EXPECT_EQ(int{snapshot.height()}, window->bounds().height());
}

}  // namespace
}  // namespace crosapi