chromium/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc

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

#include "chrome/browser/media/webrtc/native_desktop_media_list.h"

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/desktop_media_list.h"
#include "chrome/test/views/chrome_views_test_base.h"
#include "content/public/browser/desktop_capture.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capture_options.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"

#if defined(USE_AURA)
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/strings/string_util_win.h"
#endif

DesktopMediaID;
_;
DoAll;

namespace {

// Aura window capture unit tests are not stable. crbug.com/602494 and
// crbug.com/603823.
// #define ENABLE_AURA_WINDOW_TESTS

static const int kDefaultWindowCount =;
#if defined(ENABLE_AURA_WINDOW_TESTS)
static const int kDefaultAuraCount = 1;
#else
static const int kDefaultAuraCount =;
#endif

#if BUILDFLAG(IS_WIN)
constexpr char kWindowTitle[] = "NativeDesktopMediaList Test Window";
constexpr wchar_t kWideWindowTitle[] = L"NativeDesktopMediaList Test Window";
constexpr wchar_t kWindowClass[] = L"NativeDesktopMediaListTestWindowClass";

struct WindowInfo {
  HWND hwnd;
  HINSTANCE window_instance;
  ATOM window_class;
};

WindowInfo CreateTestWindow() {
  WindowInfo info;
  ::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
                          GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                      reinterpret_cast<LPCWSTR>(&DefWindowProc),
                      &info.window_instance);

  WNDCLASS window_class = {};
  window_class.hInstance = info.window_instance;
  window_class.lpfnWndProc = &DefWindowProc;
  window_class.lpszClassName = kWindowClass;
  info.window_class = ::RegisterClass(&window_class);

  info.hwnd =
      ::CreateWindow(kWindowClass, kWideWindowTitle, WS_OVERLAPPEDWINDOW,
                     CW_USEDEFAULT, CW_USEDEFAULT, /*width=*/100,
                     /*height=*/100, /*parent_window=*/nullptr,
                     /*menu_bar=*/nullptr, info.window_instance,
                     /*additional_params=*/nullptr);

  ::ShowWindow(info.hwnd, SW_SHOWNORMAL);
  ::UpdateWindow(info.hwnd);
  return info;
}

void DestroyTestWindow(WindowInfo info) {
  ::DestroyWindow(info.hwnd);
  ::UnregisterClass(MAKEINTATOM(info.window_class), info.window_instance);
}
#endif  // BUILDFLAG(IS_WIN)

// Returns the given index, offset by a fixed value such that it does not
// collide with Aura window IDs. Intended for usage with indices that are passed
// to AddNativeWindow().
int WindowIndex(int index) {}

class MockObserver : public DesktopMediaListObserver {};

class FakeScreenCapturer : public ThumbnailCapturer {};

class FakeWindowCapturer : public ThumbnailCapturer {};

}  // namespace

ACTION_P2(CheckListSize, model, expected_list_size) {}

ACTION_P2(QuitRunLoop, task_runner, run_loop) {}

class NativeDesktopMediaListTest : public ChromeViewsTestBase {};

TEST_F(NativeDesktopMediaListTest, Windows) {}

TEST_F(NativeDesktopMediaListTest, ScreenOnly) {}

// Verifies that the window specified with SetViewDialogWindowId() is filtered
// from the results.
TEST_F(NativeDesktopMediaListTest, WindowFiltering) {}

TEST_F(NativeDesktopMediaListTest, AddNativeWindow) {}

#if defined(ENABLE_AURA_WINDOW_TESTS)
TEST_F(NativeDesktopMediaListTest, AddAuraWindow) {
  AddWindowsAndVerify(false);

  base::RunLoop run_loop;

  const int index = kDefaultWindowCount;
  EXPECT_CALL(observer_, OnSourceAdded(index))
      .WillOnce(
          DoAll(CheckListSize(kDefaultWindowCount + 1),
                QuitRunLoop(base::SingleThreadTaskRunner::GetCurrentDefault(),
                            &run_loop)));

  AddAuraWindow();
  window_capturer_->SetWindowList(window_list_);

  run_loop.Run();

  int native_id = window_list_.back().id;
  EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
  EXPECT_EQ(model_->GetSource(index).id.id, native_id);
  EXPECT_EQ(model_->GetSource(index).id.window_id,
            native_aura_id_map_[native_id]);
}
#endif  // defined(ENABLE_AURA_WINDOW_TESTS)

TEST_F(NativeDesktopMediaListTest, RemoveNativeWindow) {}

#if defined(ENABLE_AURA_WINDOW_TESTS)
TEST_F(NativeDesktopMediaListTest, RemoveAuraWindow) {
  AddWindowsAndVerify(false);

  base::RunLoop run_loop;

  int aura_window_start_index = kDefaultWindowCount - kDefaultAuraCount;
  EXPECT_CALL(observer_, OnSourceRemoved(aura_window_start_index))
      .WillOnce(
          DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
                QuitRunLoop(base::SingleThreadTaskRunner::GetCurrentDefault(),
                            &run_loop)));

  RemoveAuraWindow(0);
  window_capturer_->SetWindowList(window_list_);

  run_loop.Run();
}
#endif  // defined(ENABLE_AURA_WINDOW_TESTS)

TEST_F(NativeDesktopMediaListTest, RemoveAllWindows) {}

TEST_F(NativeDesktopMediaListTest, UpdateTitle) {}

TEST_F(NativeDesktopMediaListTest, UpdateThumbnail) {}

TEST_F(NativeDesktopMediaListTest, MoveWindow) {}

// This test verifies that webrtc::DesktopCapturer::CaptureFrame() is not
// called when the thumbnail size is empty.
TEST_F(NativeDesktopMediaListTest, EmptyThumbnail) {}

#if BUILDFLAG(IS_WIN)
TEST_F(NativeDesktopMediaListTest, GetSourceListAvoidsDeadlock) {
  // We need a real window so we can send a message and reproduce the deadlock
  // scenario. This window must be created on a different thread than from where
  // `GetSourceList` will be called. Otherwise, it can directly invoke the
  // window procedure and avoid the deadlock.
  base::Thread window_thread("GetSourceListDeadlockTestWindowThread");
  window_thread.Start();
  base::RunLoop run_loop;
  WindowInfo info;
  window_thread.task_runner()->PostTaskAndReplyWithResult(
      FROM_HERE, base::BindOnce(&CreateTestWindow),
      base::BindLambdaForTesting([&](WindowInfo window_info) {
        info = window_info;
        run_loop.Quit();
      }));
  // After this point, the window will be unresponsive because we've quit its
  // message loop. This means any messages sent to the window will cause a
  // deadlock.
  run_loop.Run();
  EXPECT_NE(info.hwnd, static_cast<HWND>(0));

  // These `options` should have the `enumerate_current_process_windows`
  // option set to false, so that `GetSourceList` won't send a `WM_GETTEXT`
  // message to our window.
  webrtc::DesktopCaptureOptions options =
      content::desktop_capture::CreateDesktopCaptureOptions();
  EXPECT_FALSE(options.enumerate_current_process_windows());
  auto window_capturer = std::make_unique<FakeWindowCapturer>(options);
  window_capturer->SetWindowList(
      {{reinterpret_cast<intptr_t>(info.hwnd), kWindowTitle}});

  // This should not hang, because we told it to ignore windows owned by the
  // current process.
  webrtc::DesktopCapturer::SourceList source_list;
  EXPECT_TRUE(window_capturer->GetSourceList(&source_list));

  window_thread.task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&DestroyTestWindow, info));
  window_thread.Stop();
}

TEST_F(NativeDesktopMediaListTest, CollectsCurrentProcessWindows) {
  // We need a real window so we can ensure windows owned by the current
  // process are picked up by `model_` even if they aren't enumerated by the
  // capturer.
  CreateRealWindow();
  CreateCapturerAndModel();
  UpdateModel();

  // Ensure that `model_` is finding and adding the window to it's sources, and
  // not getting it from the capturer.
  webrtc::DesktopCapturer::SourceList source_list;
  EXPECT_TRUE(window_capturer_->GetSourceList(&source_list));
  EXPECT_EQ(source_list.size(), 0ull);

  content::DesktopMediaID::Id window_id =
      reinterpret_cast<intptr_t>(window_info_.hwnd);
  DesktopMediaList::Source source = GetSourceFromModel(window_id);
  EXPECT_EQ(source.id.id, window_id);
  EXPECT_STREQ(base::as_wcstr(source.name.c_str()), kWideWindowTitle);
}

TEST_F(NativeDesktopMediaListTest, MinimizedCurrentProcessWindows) {
  CreateRealWindow();
  CreateCapturerAndModel();

  webrtc::DesktopCapturer::SourceList source_list;
  EXPECT_TRUE(window_capturer_->GetSourceList(&source_list));
  EXPECT_EQ(source_list.size(), 0ull);

  // If we minimize the window it should not appear in `model_`s sources.
  ::ShowWindow(window_info_.hwnd, SW_MINIMIZE);
  UpdateModel();
  DesktopMediaList::Source source =
      GetSourceFromModel(reinterpret_cast<intptr_t>(window_info_.hwnd));

  // We expect the source is not found.
  EXPECT_EQ(source.id.id, content::DesktopMediaID::kNullId);
}
#endif  // BUILDFLAG(IS_WIN)

class DelegatedFakeScreenCapturer
    : public FakeScreenCapturer,
      public webrtc::DelegatedSourceListController {};

class NativeDesktopMediaListDelegatedTest : public ChromeViewsTestBase {};

TEST_F(NativeDesktopMediaListDelegatedTest, Selection) {}

TEST_F(NativeDesktopMediaListDelegatedTest, Cancelled) {}

TEST_F(NativeDesktopMediaListDelegatedTest, Error) {}

// Verify that all calls to FocusList are passed on unless there is a selection.
TEST_F(NativeDesktopMediaListDelegatedTest, ShowRepeatedlyNotForced) {}

// Verify that all calls to HideList are passed on.
TEST_F(NativeDesktopMediaListDelegatedTest, HideRepeatedly) {}

TEST_F(NativeDesktopMediaListDelegatedTest, FocusAfterSelect) {}

// Verify that ClearSelection is a no-op if there is not a selection or if it is
// not focused.
TEST_F(NativeDesktopMediaListDelegatedTest, ClearSelectionNoOp) {}