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

#include "content/browser/media/capture_handle_manager.h"

#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

using Callback = CaptureHandleManager::DeviceCaptureHandleChangeCallback;
using blink::MediaStreamDevice;
using testing::_;

const std::string kLabel = "noladaleybeceipretsamrehtonatey";

MATCHER(IsNullCaptureHandle, "") {
  static_assert(
      std::is_same<decltype(arg), const media::mojom::CaptureHandlePtr&>::value,
      "Matcher applied to incorrect type.");

  return !arg;
}

MATCHER_P2(IsCaptureHandle, expected_origin, expected_handle, "") {
  if (!arg) {
    return false;
  }

  if (arg->origin.opaque() != expected_origin.opaque()) {
    return false;  // One is empty, the other is non-empty.
  }

  // Either both are opaque or neither is. We only compare non-opaque origins.
  if (!expected_origin.opaque()) {
    if (arg->origin != expected_origin) {
      return false;
    }
  }

  return arg->capture_handle == expected_handle;
}

media::mojom::CaptureHandlePtr MakeCaptureHandle(url::Origin origin,
                                                 const std::u16string& handle) {
  auto capture_handle = media::mojom::CaptureHandle::New();
  capture_handle->origin = origin;
  capture_handle->capture_handle = handle;
  return capture_handle;
}

media::mojom::CaptureHandlePtr MakeCaptureHandle(const std::u16string& handle) {
  return MakeCaptureHandle(url::Origin(), handle);
}

blink::mojom::CaptureHandleConfigPtr MakePermissiveConfigWithHandle(
    const std::u16string& handle = u"") {
  auto ptr = blink::mojom::CaptureHandleConfig::New();
  ptr->expose_origin = true;
  ptr->capture_handle = handle;
  ptr->all_origins_permitted = true;
  return ptr;
}

blink::mojom::CaptureHandleConfigPtr MakeRestrictiveConfigWithHandle(
    const std::vector<url::Origin> permitted_origins,
    const std::u16string& handle = u"") {
  auto ptr = blink::mojom::CaptureHandleConfig::New();
  ptr->expose_origin = true;
  ptr->capture_handle = handle;
  ptr->all_origins_permitted = false;
  ptr->permitted_origins = permitted_origins;
  return ptr;
}

class CallbackHelper {
 public:
  MOCK_METHOD3(Method,
               void(const std::string& label,
                    blink::mojom::MediaStreamType type,
                    media::mojom::CaptureHandlePtr capture_handle));

  Callback AsCallback() {
    return base::BindRepeating(&CallbackHelper::Method, base::Unretained(this));
  }
};

}  // namespace

class CaptureHandleManagerTest : public RenderViewHostImplTestHarness {
 public:
  std::unique_ptr<TestWebContents> MakeTestWebContents() {
    scoped_refptr<SiteInstance> instance =
        SiteInstance::Create(GetBrowserContext());
    instance->GetOrCreateProcessForTesting()->Init();

    return TestWebContents::Create(GetBrowserContext(), std::move(instance));
  }

  CallbackHelper& MakeCallbackHelper() {
    CHECK_LT(callback_helper_count_, kMaxCallbackHelpers);
    return callback_helpers_[++callback_helper_count_];
  }

  MediaStreamDevice& MakeDevice(
      RenderFrameHost* frame,
      media::mojom::CaptureHandlePtr capture_handle = nullptr) {
    CHECK_LT(device_count_, kMaxDevices);
    auto& device = devices_[++device_count_];

    const WebContentsMediaCaptureId id(frame->GetProcess()->GetDeprecatedID(),
                                       frame->GetRoutingID());
    device.id = id.ToString();
    device.display_media_info = media::mojom::DisplayMediaInformation::New();
    device.display_media_info->capture_handle = std::move(capture_handle);

    return device;
  }

  MediaStreamDevice& MakeDevice(
      std::unique_ptr<TestWebContents>& web_contents,
      media::mojom::CaptureHandlePtr capture_handle = nullptr) {
    return MakeDevice(web_contents->GetPrimaryMainFrame(),
                      std::move(capture_handle));
  }

 protected:
  CaptureHandleManager manager_;

 private:
  static constexpr size_t kMaxCallbackHelpers = 10;
  std::array<CallbackHelper, kMaxCallbackHelpers> callback_helpers_;
  size_t callback_helper_count_ = 0;

  static constexpr size_t kMaxDevices = 10;
  std::array<MediaStreamDevice, kMaxDevices> devices_;
  size_t device_count_ = 0;
};

TEST_F(CaptureHandleManagerTest,
       OnCaptureStartedProducesNoCallbackIfDeviceHasNoCaptureHandle) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  auto& callback_helper = MakeCallbackHelper();
  EXPECT_CALL(callback_helper, Method(_, _, _)).Times(0);
  manager_.OnCaptureStarted(kLabel, MakeDevice(captured),
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());
}

TEST_F(CaptureHandleManagerTest,
       OnCaptureStartedProducesCallbackIfDeviceHasOldCaptureHandle) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  auto& captured_device = MakeDevice(captured, MakeCaptureHandle(u"same"));

  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakePermissiveConfigWithHandle(u"same"));

  auto& callback_helper = MakeCallbackHelper();
  EXPECT_CALL(callback_helper, Method(_, _, _)).Times(0);
  manager_.OnCaptureStarted(kLabel, captured_device,
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());
}

TEST_F(CaptureHandleManagerTest,
       OnCaptureStartedProducesCallbackIfDeviceHasNewCaptureHandle) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  auto& captured_device = MakeDevice(captured, MakeCaptureHandle(u"old"));

  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakePermissiveConfigWithHandle(u"new"));

  auto& callback_helper = MakeCallbackHelper();
  EXPECT_CALL(callback_helper, Method(kLabel, captured_device.type,
                                      IsCaptureHandle(url::Origin(), u"new")))
      .Times(1);
  manager_.OnCaptureStarted(kLabel, captured_device,
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());
}

TEST_F(CaptureHandleManagerTest, CallbackInvokedWhenCaptureHandleChanges) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  auto& captured_device = MakeDevice(captured, MakeCaptureHandle(u"before"));
  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakePermissiveConfigWithHandle(u"before"));

  auto& callback_helper = MakeCallbackHelper();
  manager_.OnCaptureStarted(kLabel, captured_device,
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());

  EXPECT_CALL(callback_helper, Method(kLabel, captured_device.type,
                                      IsCaptureHandle(url::Origin(), u"after")))
      .Times(1);
  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakePermissiveConfigWithHandle(u"after"));
}

TEST_F(CaptureHandleManagerTest, CaptureHandleResetByNavigation) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  const GURL kGurl1("https://origin1.com");
  const GURL kGurl2("https://origin2.com");
  const url::Origin kOrigin1 = url::Origin::Create(kGurl1);

  captured->NavigateAndCommit(kGurl1);
  auto& captured_device =
      MakeDevice(captured, MakeCaptureHandle(kOrigin1, u"handle"));
  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakePermissiveConfigWithHandle(u"handle"));

  auto& callback_helper = MakeCallbackHelper();
  manager_.OnCaptureStarted(kLabel, captured_device,
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());

  EXPECT_CALL(callback_helper,
              Method(kLabel, captured_device.type, IsNullCaptureHandle()))
      .Times(1);
  captured->NavigateAndCommit(kGurl2);
}

TEST_F(CaptureHandleManagerTest,
       CallbackNotInvokedWhenConfigDisallowsCapturer) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  const GURL kCapturedUrl("https://captured.com");
  captured->NavigateAndCommit(kCapturedUrl);

  const GURL kDisallowedUrl("https://disallowed.com");
  capturer->NavigateAndCommit(kDisallowedUrl);

  const GURL kAllowedUrl("https://allowed.com");
  const url::Origin kAllowedOrigin = url::Origin::Create(kAllowedUrl);

  auto& captured_device = MakeDevice(captured);

  auto& callback_helper = MakeCallbackHelper();
  manager_.OnCaptureStarted(kLabel, captured_device,
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());

  EXPECT_CALL(callback_helper, Method(_, _, _)).Times(0);
  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakeRestrictiveConfigWithHandle({kAllowedOrigin}, u"handle"));
}

TEST_F(CaptureHandleManagerTest, CallbackInvokedWhenConfigAllowsCapturer) {
  auto captured = MakeTestWebContents();
  auto capturer = MakeTestWebContents();

  const GURL kCapturedUrl("https://captured.com");
  const url::Origin kCapturedOrigin = url::Origin::Create(kCapturedUrl);
  captured->NavigateAndCommit(kCapturedUrl);

  const GURL kAllowedUrl("https://allowed.com");
  const url::Origin kAllowedOrigin = url::Origin::Create(kAllowedUrl);
  capturer->NavigateAndCommit(kAllowedUrl);

  auto& captured_device = MakeDevice(captured);

  auto& callback_helper = MakeCallbackHelper();
  manager_.OnCaptureStarted(kLabel, captured_device,
                            capturer->GetPrimaryMainFrame()->GetGlobalId(),
                            callback_helper.AsCallback());

  EXPECT_CALL(callback_helper,
              Method(kLabel, captured_device.type,
                     IsCaptureHandle(kCapturedOrigin, u"handle")))
      .Times(1);
  captured->GetPrimaryPage().SetCaptureHandleConfig(
      MakeRestrictiveConfigWithHandle({kAllowedOrigin}, u"handle"));
}

}  // namespace content
