// Copyright 2019 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/hid/hid_chooser_context.h"

#include <array>
#include <optional>
#include <string_view>

#include "base/barrier_closure.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/hid/hid_chooser_context_factory.h"
#include "chrome/browser/hid/mock_hid_device_observer.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/test/object_permission_context_base_mock_permission_observer.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/common/extension_features.h"
#include "google_apis/gaia/gaia_id.h"
#include "services/device/public/cpp/device_features.h"
#include "services/device/public/cpp/hid/hid_blocklist.h"
#include "services/device/public/cpp/test/fake_hid_manager.h"
#include "services/device/public/mojom/hid.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/scoped_user_manager.h"
#endif

using ::base::test::ParseJson;
using ::base::test::RunClosure;

namespace {

// The device IDs used by the simulated HID device.
constexpr uint16_t kTestVendorId = 0x1234;
constexpr uint16_t kTestProductId = 0xabcd;
constexpr char kTestSerialNumber[] = "serial-number";
constexpr char kTestProductName[] = "product-name";
constexpr auto kTestPhysicalDeviceIds = std::to_array<const char*>(
    {"physical-device-id-1", "physical-device-id-2"});
constexpr char kTestUserEmail[] = "user@example.com";

// The HID usages assigned to the top-level collection of the simulated device.
constexpr uint16_t kTestUsagePage = device::mojom::kPageGenericDesktop;
constexpr uint16_t kTestUsage = device::mojom::kGenericDesktopGamePad;

#if BUILDFLAG(ENABLE_EXTENSIONS)
constexpr char kTestExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

// Main text fixture.
class HidChooserContextTestBase {
 public:
  HidChooserContextTestBase() {
#if !BUILDFLAG(IS_ANDROID)
    scoped_feature_list_.InitAndEnableFeature(
        features::kSecurityKeyHidInterfacesAreFido);
#endif  // !BUILDFLAG(IS_ANDROID)
  }

  HidChooserContextTestBase(const HidChooserContextTestBase&) = delete;
  HidChooserContextTestBase& operator=(const HidChooserContextTestBase&) =
      delete;
  ~HidChooserContextTestBase() = default;

  void DoSetUp(bool is_affiliated, bool login_user) {
    auto* profile_name = kTestUserEmail;
#if BUILDFLAG(IS_CHROMEOS)
    if (login_user) {
      const GaiaId kTestUserGaiaId("1111111111");
      auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>();
      auto* fake_user_manager_ptr = fake_user_manager.get();
      scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
          std::move(fake_user_manager));

      auto account_id =
          AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId);
      fake_user_manager_ptr->AddUserWithAffiliation(account_id, is_affiliated);
      fake_user_manager_ptr->LoginUser(account_id);
    } else {
      profile_name = ash::kSigninBrowserContextBaseName;
    }
#endif  // BUILDFLAG(IS_CHROMEOS)

    testing_profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(testing_profile_manager_->SetUp());
    profile_ = testing_profile_manager_->CreateTestingProfile(profile_name);
    ASSERT_TRUE(profile_);

    mojo::PendingRemote<device::mojom::HidManager> hid_manager;
    hid_manager_.Bind(hid_manager.InitWithNewPipeAndPassReceiver());
    context_ = HidChooserContextFactory::GetForProfile(profile_);

    // Connect the HidManager and ensure we've received the initial enumeration
    // before continuing.
    base::RunLoop run_loop;
    context_->SetHidManagerForTesting(
        std::move(hid_manager),
        base::BindLambdaForTesting(
            [&run_loop](std::vector<device::mojom::HidDeviceInfoPtr> devices) {
              run_loop.Quit();
            }));
    run_loop.Run();

    scoped_permission_observation_.Observe(context_.get());
    scoped_device_observation_.Observe(context_.get());
  }

  void DoTearDown() {
    // Because HidBlocklist is a singleton it must be cleared after tests run to
    // prevent leakage between tests.
    feature_list_.Reset();
    device::HidBlocklist::Get().ResetToDefaultValuesForTest();
  }

  HidChooserContext* context() { return context_; }
  permissions::MockPermissionObserver& permission_observer() {
    return permission_observer_;
  }
  MockHidDeviceObserver& device_observer() { return device_observer_; }

  device::mojom::HidDeviceInfoPtr CreateDevice(
      std::string_view serial_number,
      const std::string& physical_device_id = kTestPhysicalDeviceIds[0]) {
    auto collection = device::mojom::HidCollectionInfo::New();
    collection->usage =
        device::mojom::HidUsageAndPage::New(kTestUsage, kTestUsagePage);
    collection->collection_type = device::mojom::kHIDCollectionTypeApplication;
    collection->input_reports.push_back(
        device::mojom::HidReportDescription::New());

    auto device = device::mojom::HidDeviceInfo::New();
    device->guid = base::Uuid::GenerateRandomV4().AsLowercaseString();
    device->physical_device_id = physical_device_id;
    device->vendor_id = kTestVendorId;
    device->product_id = kTestProductId;
    device->product_name = kTestProductName;
    device->serial_number = std::string{serial_number};
    device->bus_type = device::mojom::HidBusType::kHIDBusTypeUSB;
    device->collections.push_back(std::move(collection));
    device->protected_input_report_ids =
        device::HidBlocklist::Get().GetProtectedReportIds(
            device::HidBlocklist::kReportTypeInput, kTestVendorId,
            kTestProductId, device->collections);
    device->protected_output_report_ids =
        device::HidBlocklist::Get().GetProtectedReportIds(
            device::HidBlocklist::kReportTypeOutput, kTestVendorId,
            kTestProductId, device->collections);
    device->protected_feature_report_ids =
        device::HidBlocklist::Get().GetProtectedReportIds(
            device::HidBlocklist::kReportTypeFeature, kTestVendorId,
            kTestProductId, device->collections);
    device->is_excluded_by_blocklist =
        device::HidBlocklist::Get().IsVendorProductBlocked(kTestVendorId,
                                                           kTestProductId);
    return device;
  }

  device::mojom::HidDeviceInfoPtr ConnectEphemeralDeviceBlocking() {
    return ConnectDeviceBlocking(CreateDevice(/*serial_number=*/""));
  }

  device::mojom::HidDeviceInfoPtr ConnectPersistentUsbDeviceBlocking() {
    return ConnectDeviceBlocking(CreateDevice(kTestSerialNumber));
  }

#if BUILDFLAG(ENABLE_EXTENSIONS)
  device::mojom::HidDeviceInfoPtr ConnectFidoDeviceBlocking() {
    auto device = CreateDevice(/*serial_number=*/"");
    device->collections[0]->usage->usage_page = device::mojom::kPageFido;
    device->collections[0]->usage->usage = 1;
    return ConnectDeviceBlocking(std::move(device));
  }

  // Security key devices with multiple USB HID interfaces may have some that
  // are not FIDO. This method creates a HidDeviceInfoPtr representing one of
  // the non-FIDO sibling interfaces of a known security key.
  device::mojom::HidDeviceInfoPtr ConnectFidoSiblingDeviceBlocking() {
    // Device identifiers for a known security key
    static constexpr uint16_t kVendorGoogle = 0x18d1;
    static constexpr uint16_t kProductTitan = 0x5026;

    auto device = CreateDevice(/*serial_number=*/"");
    device->vendor_id = kVendorGoogle;
    device->product_id = kProductTitan;
    device->is_excluded_by_blocklist =
        device::HidBlocklist::Get().IsVendorProductBlocked(kVendorGoogle,
                                                           kProductTitan);
    device->collections[0]->usage->usage_page = device::mojom::kPageVendor;
    device->collections[0]->usage->usage = 1;
    return ConnectDeviceBlocking(std::move(device));
  }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

  device::mojom::HidDeviceInfoPtr ConnectDeviceBlocking(
      device::mojom::HidDeviceInfoPtr device) {
    base::test::TestFuture<device::mojom::HidDeviceInfoPtr> future_device;
    EXPECT_CALL(device_observer_, OnDeviceAdded).WillOnce([&](const auto& d) {
      future_device.SetValue(d.Clone());
    });
    hid_manager_.AddDevice(std::move(device));
    return future_device.Take();
  }

  device::mojom::HidDeviceInfoPtr DisconnectDeviceBlocking(
      const std::string& device_guid) {
    base::test::TestFuture<device::mojom::HidDeviceInfoPtr> future_device;
    EXPECT_CALL(device_observer_, OnDeviceRemoved).WillOnce([&](const auto& d) {
      future_device.SetValue(d.Clone());
    });
    hid_manager_.RemoveDevice(device_guid);
    return future_device.Take();
  }

  device::mojom::HidDeviceInfoPtr UpdateDeviceBlocking(
      device::mojom::HidDeviceInfoPtr device) {
    base::test::TestFuture<device::mojom::HidDeviceInfoPtr> future_device;
    EXPECT_CALL(device_observer_, OnDeviceChanged).WillOnce([&](const auto& d) {
      future_device.SetValue(d.Clone());
    });
    hid_manager_.ChangeDevice(std::move(device));
    return future_device.Take();
  }

  void SimulateHidManagerConnectionError() {
    hid_manager_.SimulateConnectionError();
  }

  void ExpectObjectPermissionChanged() {
    EXPECT_CALL(permission_observer_,
                OnObjectPermissionChanged(
                    std::make_optional(ContentSettingsType::HID_GUARD),
                    ContentSettingsType::HID_CHOOSER_DATA));
  }

  void GrantDevicePermissionBlocking(
      const url::Origin& origin,
      const device::mojom::HidDeviceInfo& device,
      const std::optional<url::Origin>& embedding_origin = std::nullopt) {
    base::RunLoop loop;
    EXPECT_CALL(permission_observer_,
                OnObjectPermissionChanged(
                    std::make_optional(ContentSettingsType::HID_GUARD),
                    ContentSettingsType::HID_CHOOSER_DATA))
        .WillOnce(RunClosure(loop.QuitClosure()));
    context()->GrantDevicePermission(origin, device, embedding_origin);
    loop.Run();
  }

  void RevokeObjectPermissionBlocking(const url::Origin& origin,
                                      const base::DictValue& object) {
    base::RunLoop loop;
    EXPECT_CALL(permission_observer_,
                OnObjectPermissionChanged(
                    std::make_optional(ContentSettingsType::HID_GUARD),
                    ContentSettingsType::HID_CHOOSER_DATA))
        .Times(testing::AtLeast(1))
        .WillOnce(RunClosure(loop.QuitClosure()));
    context()->RevokeObjectPermission(origin, object);
    loop.Run();
  }

  void SetDynamicBlocklist(std::string_view value) {
    feature_list_.Reset();

    std::map<std::string, std::string> parameters;
    parameters[device::kWebHidBlocklistAdditions.name] = std::string{value};
    feature_list_.InitWithFeaturesAndParameters(
        {{device::kWebHidBlocklist, parameters}}, {});

    device::HidBlocklist::Get().ResetToDefaultValuesForTest();
  }

  void SetContentSettingDefaultForOrigin(const url::Origin& origin,
                                         ContentSetting content_setting) {
    HostContentSettingsMapFactory::GetForProfile(profile_)
        ->SetContentSettingDefaultScope(origin.GetURL(), origin.GetURL(),
                                        ContentSettingsType::HID_GUARD,
                                        content_setting);
  }

  void SetContentSettingDefaultPolicy(ContentSetting content_setting) {
    profile_->GetTestingPrefService()->SetManagedPref(
        prefs::kManagedDefaultWebHidGuardSetting,
        std::make_unique<base::Value>(content_setting));
  }

  void SetAskForUrlsPolicy(std::string_view policy) {
    profile_->GetTestingPrefService()->SetManagedPref(
        prefs::kManagedWebHidAskForUrls, ParseJson(policy));
  }

  void SetBlockedForUrlsPolicy(std::string_view policy) {
    profile_->GetTestingPrefService()->SetManagedPref(
        prefs::kManagedWebHidBlockedForUrls, ParseJson(policy));
  }

  void SetAllowDevicesForUrlsPolicy(std::string_view policy) {
    TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref(
        prefs::kManagedWebHidAllowDevicesForUrls, ParseJson(policy));
  }

  void SetAllowDevicesForUrlsOnLoginScreenPolicy(std::string_view policy) {
    TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref(
        prefs::kManagedWebHidAllowDevicesForUrlsOnLoginScreen,
        ParseJson(policy));
  }

  void SetAllowDevicesWithHidUsagesForUrlsPolicy(std::string_view policy) {
    TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref(
        prefs::kManagedWebHidAllowDevicesWithHidUsagesForUrls,
        ParseJson(policy));
  }

  void SetAllowAllDevicesForUrlsPolicy(std::string_view policy) {
    TestingBrowserProcess::GetGlobal()->GetTestingLocalState()->SetManagedPref(
        prefs::kManagedWebHidAllowAllDevicesForUrls, ParseJson(policy));
  }

 private:
  device::FakeHidManager hid_manager_;
  content::BrowserTaskEnvironment task_environment_;
  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<TestingProfileManager> testing_profile_manager_;
  raw_ptr<TestingProfile> profile_ = nullptr;

#if BUILDFLAG(IS_CHROMEOS)
  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
#endif

  raw_ptr<HidChooserContext> context_;
  permissions::MockPermissionObserver permission_observer_;
  base::ScopedObservation<
      permissions::ObjectPermissionContextBase,
      permissions::ObjectPermissionContextBase::PermissionObserver>
      scoped_permission_observation_{&permission_observer_};
  MockHidDeviceObserver device_observer_;
  base::ScopedObservation<HidChooserContext, HidChooserContext::DeviceObserver>
      scoped_device_observation_{&device_observer_};
  base::test::ScopedFeatureList scoped_feature_list_;
};

class HidChooserContextTest : public HidChooserContextTestBase,
                              public testing::Test {
 public:
  void SetUp() override {
    DoSetUp(/*is_affiliated=*/true, /*login_user=*/true);
  }
  void TearDown() override { DoTearDown(); }
};

}  // namespace

TEST_F(HidChooserContextTest, GrantAndRevokeEphemeralDevice) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device that is only eligible for ephemeral permissions.
  auto device = ConnectEphemeralDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant an ephemeral permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));

  std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects =
      context()->GetGrantedObjects(kOrigin);
  ASSERT_EQ(1u, origin_objects.size());

  std::vector<std::unique_ptr<HidChooserContext::Object>> objects =
      context()->GetAllGrantedObjects();
  ASSERT_EQ(1u, objects.size());
  EXPECT_EQ(kOrigin.GetURL(), objects[0]->origin);
  EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
  EXPECT_EQ(content_settings::SettingSource::kUser, objects[0]->source);
  EXPECT_FALSE(objects[0]->incognito);

  // Revoke the permission.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin));
  RevokeObjectPermissionBlocking(kOrigin, objects[0]->value);
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, GrantAndForgetEphemeralDevice) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device with multiple HID interfaces that is only eligible for
  // ephemeral permissions.
  auto device1 = ConnectEphemeralDeviceBlocking();
  auto device2 = ConnectEphemeralDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant ephemeral permissions.
  GrantDevicePermissionBlocking(kOrigin, *device1);
  GrantDevicePermissionBlocking(kOrigin, *device2);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(2u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(2u, context()->GetAllGrantedObjects().size());

  // Forget the ephemeral device.
  base::RunLoop permissions_revoked_loop;
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin))
      .WillOnce(RunClosure(permissions_revoked_loop.QuitClosure()));
  EXPECT_CALL(permission_observer(),
              OnObjectPermissionChanged(
                  std::make_optional(ContentSettingsType::HID_GUARD),
                  ContentSettingsType::HID_CHOOSER_DATA));
  context()->RevokeDevicePermission(kOrigin, *device1);
  permissions_revoked_loop.Run();

  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, GrantTwoEphemeralDevicesForgetOne) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect two devices that are only eligible for ephemeral permissions.
  auto device1 = ConnectDeviceBlocking(CreateDevice(
      /*serial_number=*/"", /*physical_device_id=*/kTestPhysicalDeviceIds[0]));
  auto device2 = ConnectDeviceBlocking(CreateDevice(
      /*serial_number=*/"", /*physical_device_id=*/kTestPhysicalDeviceIds[1]));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant ephemeral permissions.
  GrantDevicePermissionBlocking(kOrigin, *device1);
  GrantDevicePermissionBlocking(kOrigin, *device2);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(2u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(2u, context()->GetAllGrantedObjects().size());

  // Forget the first device.
  base::RunLoop permissions_revoked_loop;
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin))
      .WillOnce(RunClosure(permissions_revoked_loop.QuitClosure()));
  EXPECT_CALL(permission_observer(),
              OnObjectPermissionChanged(
                  std::make_optional(ContentSettingsType::HID_GUARD),
                  ContentSettingsType::HID_CHOOSER_DATA));
  context()->RevokeDevicePermission(kOrigin, *device1);
  permissions_revoked_loop.Run();

  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, GrantAndDisconnectEphemeralDevice) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device that is only eligible for ephemeral permissions.
  auto device = ConnectEphemeralDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant an ephemeral permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));

  auto origin_objects = context()->GetGrantedObjects(kOrigin);
  ASSERT_EQ(1u, origin_objects.size());

  auto objects = context()->GetAllGrantedObjects();
  ASSERT_EQ(1u, objects.size());
  EXPECT_EQ(kOrigin.GetURL(), objects[0]->origin);
  EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
  EXPECT_EQ(content_settings::SettingSource::kUser, objects[0]->source);
  EXPECT_FALSE(objects[0]->incognito);

  // Disconnect the device. Because an ephemeral permission was granted, the
  // permission should be revoked on disconnect.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin));
  EXPECT_CALL(permission_observer(),
              OnObjectPermissionChanged(
                  std::make_optional(ContentSettingsType::HID_GUARD),
                  ContentSettingsType::HID_CHOOSER_DATA));
  DisconnectDeviceBlocking(device->guid);
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, GrantDisconnectRevokeUsbPersistentDevice) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a USB device eligible for persistent permissions.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant a persistent permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));

  auto origin_objects = context()->GetGrantedObjects(kOrigin);
  ASSERT_EQ(1u, origin_objects.size());
  auto objects = context()->GetAllGrantedObjects();
  ASSERT_EQ(1u, objects.size());
  EXPECT_EQ(kOrigin.GetURL(), objects[0]->origin);
  EXPECT_EQ(origin_objects[0]->value, objects[0]->value);
  EXPECT_EQ(content_settings::SettingSource::kUser, objects[0]->source);
  EXPECT_FALSE(objects[0]->incognito);

  // Disconnect the device. The permission should not be revoked.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin)).Times(0);
  DisconnectDeviceBlocking(device->guid);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  objects = context()->GetAllGrantedObjects();
  EXPECT_EQ(1u, objects.size());

  // Revoke the persistent permission.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin));
  RevokeObjectPermissionBlocking(kOrigin, objects[0]->value);

  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, GrantForgetUsbPersistentDevice) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a USB device with multiple HID interfaces eligible for
  // persistent permissions.
  auto device1 = ConnectPersistentUsbDeviceBlocking();
  auto device2 = ConnectPersistentUsbDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2));

  // Grant persistent permissions.
  GrantDevicePermissionBlocking(kOrigin, *device1);
  GrantDevicePermissionBlocking(kOrigin, *device2);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  auto objects = context()->GetAllGrantedObjects();
  ASSERT_EQ(1u, objects.size());

  // Forget the device by revoking the persistent permission.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin));
  RevokeObjectPermissionBlocking(kOrigin, objects[0]->value);
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device1));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device2));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, GuardPermission) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device that is only eligible for ephemeral permissions.
  auto device = ConnectEphemeralDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant an ephemeral device permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());

  // Set the guard permission to CONTENT_SETTING_BLOCK.
  SetContentSettingDefaultForOrigin(kOrigin, CONTENT_SETTING_BLOCK);

  // Check that the device permission is no longer granted.
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, ConnectionErrorWithEphemeralPermission) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device that is only eligible for ephemeral permissions.
  auto device = ConnectEphemeralDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant an ephemeral device permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());

  // Simulate a connection error. The ephemeral permission should be revoked.
  base::RunLoop loop;
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kOrigin))
      .WillOnce(RunClosure(loop.QuitClosure()));
  EXPECT_CALL(permission_observer(),
              OnObjectPermissionChanged(
                  std::make_optional(ContentSettingsType::HID_GUARD),
                  ContentSettingsType::HID_CHOOSER_DATA));
  EXPECT_CALL(device_observer(), OnHidManagerConnectionError());
  SimulateHidManagerConnectionError();
  loop.Run();

  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, ConnectionErrorWithPersistentPermission) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a USB device eligible for persistent permissions.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant a persistent device permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());

  // Simulate a connection error. The persistent permission should not be
  // affected.
  base::RunLoop loop;
  EXPECT_CALL(device_observer(), OnHidManagerConnectionError())
      .WillOnce(RunClosure(loop.QuitClosure()));
  EXPECT_CALL(permission_observer(),
              OnObjectPermissionChanged(
                  std::make_optional(ContentSettingsType::HID_GUARD),
                  ContentSettingsType::HID_CHOOSER_DATA));
  SimulateHidManagerConnectionError();
  loop.Run();

  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
}

namespace {

device::mojom::HidDeviceInfoPtr CreateDeviceWithOneCollection(
    const std::string& guid) {
  auto device_info = device::mojom::HidDeviceInfo::New();
  device_info->guid = guid;
  auto collection = device::mojom::HidCollectionInfo::New();
  collection->usage = device::mojom::HidUsageAndPage::New(1, 1);
  collection->input_reports.push_back(
      device::mojom::HidReportDescription::New());
  device_info->collections.push_back(std::move(collection));
  return device_info;
}

device::mojom::HidDeviceInfoPtr CreateDeviceWithTwoCollections(
    const std::string& guid) {
  auto device_info = CreateDeviceWithOneCollection(guid);
  auto collection = device::mojom::HidCollectionInfo::New();
  collection->usage = device::mojom::HidUsageAndPage::New(2, 2);
  collection->output_reports.push_back(
      device::mojom::HidReportDescription::New());
  device_info->collections.push_back(std::move(collection));
  return device_info;
}

}  // namespace

TEST_F(HidChooserContextTest, AddChangeRemoveDevice) {
  const char kTestGuid[] = "guid";

  EXPECT_FALSE(context()->GetDeviceInfo(kTestGuid));

  // Connect a partially-initialized device.
  auto partial_device =
      ConnectDeviceBlocking(CreateDeviceWithOneCollection(kTestGuid));
  EXPECT_EQ(partial_device->guid, kTestGuid);
  EXPECT_EQ(partial_device->collections.size(), 1u);
  auto* device_info = context()->GetDeviceInfo(kTestGuid);
  ASSERT_TRUE(device_info);
  EXPECT_EQ(device_info->collections.size(), 1u);

  // Update the device to add another collection.
  auto complete_device =
      UpdateDeviceBlocking(CreateDeviceWithTwoCollections(kTestGuid));
  EXPECT_EQ(complete_device->guid, kTestGuid);
  EXPECT_EQ(complete_device->collections.size(), 2u);
  device_info = context()->GetDeviceInfo(kTestGuid);
  ASSERT_TRUE(device_info);
  EXPECT_EQ(device_info->collections.size(), 2u);

  // Disconnect the device.
  auto removed_device = DisconnectDeviceBlocking(complete_device->guid);
  EXPECT_EQ(removed_device->guid, kTestGuid);
  EXPECT_EQ(removed_device->collections.size(), 2u);
  ASSERT_FALSE(context()->GetDeviceInfo(kTestGuid));
}

namespace {

struct BlocklistTestData {
  const char* blocklist;
  bool expect_device_permission;
} kBlocklistTestData[]{
    {nullptr, true},          {"", true},
    {"1234:abcd::::", false}, {"1234:0001::::", true},
    {"1234:::::", false},     {"2468:::::", true},
    {"::0001:0005::", true},  {"::0001:0006::", true},
    {"::0001:::", true},      {"::ff00:::", true},
};

class HidChooserContextBlocklistTest
    : public HidChooserContextTestBase,
      public testing::TestWithParam<BlocklistTestData> {
 public:
  HidChooserContextBlocklistTest() = default;

  void SetUp() override {
    DoSetUp(/*is_affiliated=*/true, /*login_user=*/true);
  }
  void TearDown() override { DoTearDown(); }
};

}  // namespace

TEST_P(HidChooserContextBlocklistTest, Blocklist) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  if (GetParam().blocklist)
    SetDynamicBlocklist(GetParam().blocklist);

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Try to grant permission.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_EQ(GetParam().expect_device_permission,
            context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
}

INSTANTIATE_TEST_SUITE_P(HidChooserContextBlocklistTestInstance,
                         HidChooserContextBlocklistTest,
                         ::testing::ValuesIn(kBlocklistTestData));

TEST_F(HidChooserContextTest, PolicyGuardPermission) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOrigin));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant permission and check that the permission was granted.
  GrantDevicePermissionBlocking(kOrigin, *device);
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOrigin));
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());

  // Set a policy to block access to HID devices. After setting the policy, no
  // permissions should be granted and requesting permissions should be blocked.
  SetContentSettingDefaultPolicy(CONTENT_SETTING_BLOCK);
  EXPECT_FALSE(context()->CanRequestObjectPermission(kOrigin));
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, PolicyAskForUrls) {
  const auto kAskOrigin = url::Origin::Create(GURL("https://ask.origin"));
  const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin"));

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin));
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant permission for kAskOrigin and kOtherOrigin to access |device|.
  GrantDevicePermissionBlocking(kAskOrigin, *device);
  GrantDevicePermissionBlocking(kOtherOrigin, *device);
  EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin));
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_TRUE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_TRUE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(2u, context()->GetAllGrantedObjects().size());

  // Set the default guard policy to "block", overriding the granted
  // permissions.
  SetContentSettingDefaultPolicy(CONTENT_SETTING_BLOCK);
  EXPECT_FALSE(context()->CanRequestObjectPermission(kAskOrigin));
  EXPECT_FALSE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Set the AskForUrls policy to allow kAskOrigin to request permissions.
  // This policy overrides the default guard policy.
  SetAskForUrlsPolicy(R"( [ "https://ask.origin" ] )");
  EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin));
  EXPECT_FALSE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_TRUE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
}

TEST_F(HidChooserContextTest, PolicyBlockedForUrls) {
  const auto kBlockedOrigin =
      url::Origin::Create(GURL("https://blocked.origin"));
  const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin"));

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_TRUE(context()->CanRequestObjectPermission(kBlockedOrigin));
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Grant permission for kBlockedOrigin and kOtherOrigin to access |device|.
  GrantDevicePermissionBlocking(kBlockedOrigin, *device);
  GrantDevicePermissionBlocking(kOtherOrigin, *device);
  EXPECT_TRUE(context()->CanRequestObjectPermission(kBlockedOrigin));
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_TRUE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_TRUE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(2u, context()->GetAllGrantedObjects().size());

  // Set the BlockedForUrls policy to block kBlockedOrigin from accessing
  // devices or requesting permissions. This policy overrides user-granted
  // permissions and the default guard setting.
  SetBlockedForUrlsPolicy(R"([ "https://blocked.origin" ])");
  EXPECT_FALSE(context()->CanRequestObjectPermission(kBlockedOrigin));
  EXPECT_TRUE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_TRUE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
}

namespace {

class HidChooserContextAffiliatedTest : public HidChooserContextTestBase,
                                        public testing::TestWithParam<bool> {
 public:
  HidChooserContextAffiliatedTest() : is_affiliated_(GetParam()) {}

  void SetUp() override { DoSetUp(is_affiliated_, /*login_user=*/true); }
  void TearDown() override { DoTearDown(); }

  bool is_affiliated() const { return is_affiliated_; }

 private:
  bool is_affiliated_;
};

}  // namespace

TEST_P(HidChooserContextAffiliatedTest, PolicyAllowForUrls) {
  const auto kBlockedOrigin =
      url::Origin::Create(GURL("https://blocked.origin"));
  const auto kAskOrigin = url::Origin::Create(GURL("https://ask.origin"));
  const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin"));

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();

  // Set the default content settings to "block" by policy, and set the
  // AskForUrls and BlockedForUrls policies to override the default for
  // kAskOrigin and kBlockedOrigin.
  SetContentSettingDefaultPolicy(CONTENT_SETTING_BLOCK);
  SetAskForUrlsPolicy(R"([ "https://ask.origin" ])");
  SetBlockedForUrlsPolicy(R"([ "https://blocked.origin" ])");
  EXPECT_FALSE(context()->CanRequestObjectPermission(kBlockedOrigin));
  EXPECT_TRUE(context()->CanRequestObjectPermission(kAskOrigin));
  EXPECT_FALSE(context()->CanRequestObjectPermission(kOtherOrigin));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Set the AllowAllDevicesForUrls policy to grant all three origins permission
  // to access any device.
  SetAllowAllDevicesForUrlsPolicy(R"(
      [
          "https://blocked.origin",
          "https://ask.origin",
          "https://other.origin"
      ])");
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(3u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowAllDevicesForUrls policy back to the default value.
  SetAllowAllDevicesForUrlsPolicy("[]");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Set the AllowDevicesForUrls policy to grant permissions to kAskOrigin and
  // kOtherOrigin by matching kTestVendorId and kTestProductId.
  SetAllowDevicesForUrlsPolicy(R"(
      [
        {
          "devices": [{ "vendor_id": 4660, "product_id": 43981 }],
          "urls": [ "https://ask.origin", "https://other.origin" ]
        }
      ])");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(2u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowDevicesForUrls policy to give permissions to kBlockedOrigin.
  // This policy overrides the BlockedForUrls policy.
  SetAllowDevicesForUrlsPolicy(R"(
      [
        {
          "devices": [{ "vendor_id": 4660, "product_id": 43981 }],
          "urls": [ "https://blocked.origin" ]
        }
      ])");
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowDevicesForUrls policy to grant permission to kOtherOrigin to
  // access devices with kTestVendorId and any product ID. A second rule grants
  // permission for kAskOrigin to access a different device with the same vendor
  // ID and a third rule grants permission for kBlockedOrigin to access a
  // different vendor ID. Only the first rule matches the device.
  SetAllowDevicesForUrlsPolicy(R"(
      [
        {
          "devices": [{ "vendor_id": 4660 }],
          "urls": [ "https://other.origin" ]
        },
        {
          "devices": [{ "vendor_id": 4660, "product_id": 1 }],
          "urls": [ "https://ask.origin" ]
        },
        {
          "devices": [{ "vendor_id": 123 }],
          "urls": [ "https://blocked.origin" ]
        }
      ])");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(3u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowDevicesForUrls policy back to the default value.
  SetAllowDevicesForUrlsPolicy("[]");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // Set the AllowDevicesWithHidUsagesForUrls policy to grant permissions to
  // kAskOrigin and kOtherOrigin by matching kTestUsagePage and kTestUsage.
  SetAllowDevicesWithHidUsagesForUrlsPolicy(R"(
      [
        {
          "usages": [{ "usage_page": 1, "usage": 5 }],
          "urls": [ "https://ask.origin", "https://other.origin" ]
        }
      ])");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(2u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowDevicesWithHidUsagesForUrls policy to give permissions to
  // kBlockedOrigin. This policy overrides the BlockedForUrls policy.
  SetAllowDevicesWithHidUsagesForUrlsPolicy(R"(
      [
        {
          "usages": [{ "usage_page": 1, "usage": 5 }],
          "urls": [ "https://blocked.origin" ]
        }
      ])");
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowDevicesWithHidUsagesForUrls policy to grant permission to
  // kOtherOrigin to access devices with any usage from kTestUsagePage. A
  // second rule grants permission for kAskOrigin to access devices with a
  // different usage from kTestUsagePage and a third rule grants permission for
  // kBlockedOrigin to access a different usage page. Only the first rule
  // matches the device.
  SetAllowDevicesWithHidUsagesForUrlsPolicy(R"(
      [
        {
          "usages": [{ "usage_page": 1 }],
          "urls": [ "https://other.origin" ]
        },
        {
          "usages": [{ "usage_page": 1, "usage": 1 }],
          "urls": [ "https://ask.origin" ]
        },
        {
          "usages": [{ "usage_page": 123 }],
          "urls": [ "https://blocked.origin" ]
        }
      ])");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_EQ(is_affiliated(),
            context()->HasDevicePermission(kOtherOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(3u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  // Set the AllowDevicesWithHidUsagesForUrls policy back to the default value.
  SetAllowDevicesWithHidUsagesForUrlsPolicy("[]");
  EXPECT_FALSE(context()->HasDevicePermission(kBlockedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kAskOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kBlockedOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kAskOrigin).size());
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOtherOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

TEST_P(HidChooserContextAffiliatedTest, BlocklistOverridesPolicy) {
  const auto kOrigin = url::Origin::Create(GURL("https://test.origin"));

  // Set the blocklist to deny access to devices with kTestVendorId and
  // kTestProductId.
  SetDynamicBlocklist("1234:abcd::::");

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // The AllowAllDevicesForUrls policy cannot override the blocklist.
  SetAllowAllDevicesForUrlsPolicy(R"([ "https://test.origin" ])");
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
    EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  SetAllowAllDevicesForUrlsPolicy("[]");
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // The AllowDevicesForUrls policy cannot override the blocklist.
  SetAllowDevicesForUrlsPolicy(R"(
      [
        {
          "devices": [{ "vendor_id": 4660, "product_id": 43981 }],
          "urls": [ "https://test.origin" ]
        }
      ])");
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
    EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  SetAllowDevicesForUrlsPolicy("[]");
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());

  // The AllowDevicesWithHidUsagesForUrls policy cannot override the blocklist.
  SetAllowDevicesWithHidUsagesForUrlsPolicy(R"(
      [
        {
          "usages": [{ "usage_page": 1, "usage": 5 }],
          "urls": [ "https://test.origin" ]
        }
      ])");
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  if (is_affiliated()) {
    EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
    EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
  } else {
    EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
    EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
  }

  SetAllowDevicesWithHidUsagesForUrlsPolicy("[]");
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(HidChooserContextTest, FidoAllowlistOverridesBlocklistDeviceIdRule) {
  const auto kFidoAllowedOrigin = url::Origin::Create(
      GURL("chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok"));
  const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin"));

  // Configure the blocklist to deny access to devices with kTestVendorId and
  // kTestProductId.
  SetDynamicBlocklist("1234:abcd::::");

  // Connect a FIDO device.
  auto device = ConnectFidoDeviceBlocking();

  // Check that the FIDO device is still blocked. Now it is blocked both for
  // being FIDO and also for matching the device ID rule.
  EXPECT_FALSE(context()->HasDevicePermission(kFidoAllowedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));

  // Granting permission to the privileged origin succeeds.
  GrantDevicePermissionBlocking(kFidoAllowedOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kFidoAllowedOrigin, *device));

  // Granting permission to the non-privileged origin fails.
  GrantDevicePermissionBlocking(kOtherOrigin, *device);
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
}

TEST_P(HidChooserContextAffiliatedTest, FidoAllowlistAndPolicy) {
  const auto kFidoAndPolicyAllowedOrigin = url::Origin::Create(
      GURL("chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok"));
  const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin"));

  // Configure the blocklist to deny access to devices with kTestVendorId and
  // kTestProductId.
  SetDynamicBlocklist("1234:abcd::::");

  SetAllowDevicesWithHidUsagesForUrlsPolicy(R"(
      [
        {
          "usages": [{ "usage_page": 61904 }],
          "urls": [ "chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok" ]
        }
      ])");

  // Connect a device matching the first policy rule. If the policy could be set
  // then the policy-granted origin should already have permission.
  auto device = ConnectFidoDeviceBlocking();
  EXPECT_EQ(is_affiliated(), context()->HasDevicePermission(
                                 kFidoAndPolicyAllowedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
}

TEST_P(HidChooserContextAffiliatedTest,
       FidoAllowlistAndPolicyAppliesToSiblingInterface) {
  const auto kFidoAndPolicyAllowedOrigin = url::Origin::Create(
      GURL("chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok"));
  const auto kOtherOrigin = url::Origin::Create(GURL("https://other.origin"));

  // Configure a policy to grant permission for a privileged origin to access a
  // blocklisted device.
  SetAllowDevicesForUrlsPolicy(R"(
      [
        {
          "devices": [{ "vendor_id": 6353, "product_id": 20518 }],
          "urls": [ "chrome-extension://ckcendljdlmgnhghiaomidhiiclmapok" ]
        }
      ])");

  // Connect a device matching the policy rule. If the policy could be set then
  // the policy-granted origin should already have permission.
  auto device = ConnectFidoSiblingDeviceBlocking();
  EXPECT_EQ(is_affiliated(), context()->HasDevicePermission(
                                 kFidoAndPolicyAllowedOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kOtherOrigin, *device));
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)

// Boolean parameter means if user is affiliated on the device. Affiliated
// users belong to the domain that owns the device and is only meaningful
// on ChromeOS.
//
// The WebHidAllowDevicesForUrls, WebHidAllowDevicesWithHidUsagesForUrls, and
// WebHidAllowAllDevicesForUrls policies only take effect for affiliated users.
INSTANTIATE_TEST_SUITE_P(
    HidChooserContextAffiliatedTestInstance,
    HidChooserContextAffiliatedTest,
#if BUILDFLAG(IS_CHROMEOS)
    testing::Values(true, false),
#else
    testing::Values(true),
#endif
    [](const testing::TestParamInfo<HidChooserContextAffiliatedTest::ParamType>&
           info) { return info.param ? "affiliated" : "unaffiliated"; });

namespace {

class HidChooserContextLoginScreenTest : public HidChooserContextTestBase,
                                         public testing::Test {
 public:
  HidChooserContextLoginScreenTest() = default;

  void SetUp() override {
    DoSetUp(/*is_affiliated=*/false, /*login_user=*/false);
  }
  void TearDown() override { DoTearDown(); }
};

}  // namespace

TEST_F(HidChooserContextLoginScreenTest, ApplyPolicyOnLoginScreen) {
  const auto kOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect a device.
  auto device = ConnectPersistentUsbDeviceBlocking();

  // Set the DeviceLoginScreenWebHidAllowDevicesForUrls policy
  SetAllowDevicesForUrlsOnLoginScreenPolicy(R"(
      [
        {
          "devices": [{ "vendor_id": 4660, "product_id": 43981 }],
          "urls": [ "https://google.com" ]
        }
      ])");

  // The policy has an effect only for IS_CHROMEOS build, otherwise it is
  // ignored.
#if BUILDFLAG(IS_CHROMEOS)
  EXPECT_TRUE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(1u, context()->GetAllGrantedObjects().size());
#else
  EXPECT_FALSE(context()->HasDevicePermission(kOrigin, *device));
  EXPECT_EQ(0u, context()->GetGrantedObjects(kOrigin).size());
  EXPECT_EQ(0u, context()->GetAllGrantedObjects().size());
#endif
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
class HidChooserContextWebViewTest : public HidChooserContextTest {
 public:
  HidChooserContextWebViewTest() {
    scoped_feature_list_.InitAndEnableFeature(
        extensions_features::kEnableWebHidInWebView);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

// Verifies that a permission for WebView can be granted successfully.
// Checks that this permission is *not* accessible outside the embedding app.
TEST_F(HidChooserContextWebViewTest, GrantDevicePermissionToWebView) {
  const auto kEmbeddingOrigin = url::Origin::Create(
      GURL("chrome-extension://" + std::string(kTestExtensionId)));
  const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect an ephemeral device and grant permission to the extension.
  auto device = ConnectEphemeralDeviceBlocking();
  GrantDevicePermissionBlocking(kEmbeddingOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kEmbeddingOrigin, *device));
  EXPECT_EQ(1u, context()->GetGrantedObjects(kEmbeddingOrigin).size());

  // Grant permission to the embedded WebView.
  GrantDevicePermissionBlocking(kWebViewOrigin, *device, kEmbeddingOrigin);
  EXPECT_TRUE(context()->HasDevicePermission(kEmbeddingOrigin, *device));
  EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                             kEmbeddingOrigin));
  // WebView permission should not leak outside the embedding app context.
  EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device));
  const auto kAnotherAppOrigin = url::Origin::Create(
      GURL("chrome-extension://abababababababababababababababababababab"));
  EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                              kAnotherAppOrigin));
}

// Tests that revoking HID device permission from an embedding application
// simultaneously revokes permissions for any associated WebViews that were
// granted access through the embedder.
TEST_F(HidChooserContextWebViewTest, RevokeDevicePermissionFromEmbedder) {
  const auto kEmbeddingOrigin = url::Origin::Create(
      GURL("chrome-extension://" + std::string(kTestExtensionId)));
  const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect an ephemeral device and grant permission to the embedder and
  // WebView.
  auto device = ConnectEphemeralDeviceBlocking();
  GrantDevicePermissionBlocking(kEmbeddingOrigin, *device);
  GrantDevicePermissionBlocking(kWebViewOrigin, *device, kEmbeddingOrigin);
  EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                             kEmbeddingOrigin));

  std::vector<std::unique_ptr<HidChooserContext::Object>> origin_objects =
      context()->GetGrantedObjects(kEmbeddingOrigin);
  ASSERT_EQ(1u, origin_objects.size());

  // Revoke permission from the embedder. WebView's permission should also be
  // revoked.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kEmbeddingOrigin));
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kWebViewOrigin));
  RevokeObjectPermissionBlocking(kEmbeddingOrigin, origin_objects[0]->value);
  EXPECT_FALSE(context()->HasDevicePermission(kEmbeddingOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                              kEmbeddingOrigin));
}

// Ensures that the revocation of HID device permission from a WebView does not
// interfere with the permissions held by the embedding application.
TEST_F(HidChooserContextWebViewTest, RevokeDevicePermissionFromWebView) {
  const auto kEmbeddingOrigin = url::Origin::Create(
      GURL("chrome-extension://" + std::string(kTestExtensionId)));
  const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect an ephemeral device and grant permission to the embedder and
  // WebView.
  auto device = ConnectEphemeralDeviceBlocking();
  GrantDevicePermissionBlocking(kEmbeddingOrigin, *device);
  GrantDevicePermissionBlocking(kWebViewOrigin, *device, kEmbeddingOrigin);
  EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                             kEmbeddingOrigin));

  // Revoke permission from the WebView. Embedder's permission should not be
  // affected.
  EXPECT_CALL(permission_observer(), OnPermissionRevoked(kWebViewOrigin));
  ExpectObjectPermissionChanged();
  context()->RevokeDevicePermission(kWebViewOrigin, *device, kEmbeddingOrigin);
  EXPECT_TRUE(context()->HasDevicePermission(kEmbeddingOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                              kEmbeddingOrigin));
}

// Confirms the strict isolation of HID device permissions granted directly to a
// website's origin. Verifies that these permissions are not accessible within
// an embedded WebView, even if belonging to the same website.
TEST_F(HidChooserContextWebViewTest, WebsitePermissionDoesNotLeakToWebView) {
  const auto kEmbeddingOrigin = url::Origin::Create(
      GURL("chrome-extension://" + std::string(kTestExtensionId)));
  const auto kWebViewOrigin = url::Origin::Create(GURL("https://google.com"));

  // Connect an ephemeral device and grant permission to the WebView's origin.
  auto device = ConnectEphemeralDeviceBlocking();
  GrantDevicePermissionBlocking(kWebViewOrigin, *device);
  EXPECT_TRUE(context()->HasDevicePermission(kWebViewOrigin, *device));
  EXPECT_FALSE(context()->HasDevicePermission(kWebViewOrigin, *device,
                                              kEmbeddingOrigin));
}
#endif  // BUILDFLAG(ENABLE_EXTENSIONS)
