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

#include "components/content_settings/browser/page_specific_content_settings.h"

#include <optional>
#include <string>
#include <variant>

#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/browsing_data/content/browsing_data_helper.h"
#include "components/content_settings/browser/test_page_specific_content_settings_delegate.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/content_settings/core/common/features.h"
#include "components/security_state/core/security_state.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/browser/cookie_access_details.h"
#include "content/public/browser/trust_token_access_details.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_access_result.h"
#include "net/cookies/cookie_options.h"
#include "net/shared_dictionary/shared_dictionary_isolation_key.h"
#include "services/network/public/mojom/shared_dictionary_access_observer.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"

namespace content_settings {

using StorageType = mojom::ContentSettingsManager::StorageType;

namespace {

class MockSiteDataObserver
    : public PageSpecificContentSettings::SiteDataObserver {
 public:
  explicit MockSiteDataObserver(content::WebContents* web_contents)
      : SiteDataObserver(web_contents) {}

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

  ~MockSiteDataObserver() override = default;

  MOCK_METHOD(void, OnSiteDataAccessed, (const AccessDetails& access_details));
  MOCK_METHOD(void, OnStatefulBounceDetected, ());
};

class MockPageSpecificContentSettingsDelegate
    : public TestPageSpecificContentSettingsDelegate {
 public:
  MockPageSpecificContentSettingsDelegate(PrefService* prefs,
                                          HostContentSettingsMap* settings_map)
      : TestPageSpecificContentSettingsDelegate(prefs, settings_map) {}
  ~MockPageSpecificContentSettingsDelegate() override = default;

  MOCK_METHOD(void, UpdateLocationBar, ());
  MOCK_METHOD(void, OnContentAllowed, (ContentSettingsType type));
  MOCK_METHOD(void, OnContentBlocked, (ContentSettingsType type));
  MOCK_METHOD(bool,
              IsFrameAllowlistedForJavaScript,
              (content::RenderFrameHost * render_frame_host));
};

blink::StorageKey CreateUnpartitionedStorageKey(const GURL& url) {
  return blink::StorageKey::CreateFirstParty(url::Origin::Create(url));
}

}  // namespace

class PageSpecificContentSettingsTest
    : public content::RenderViewHostTestHarness {
 public:
  PageSpecificContentSettingsTest()
      : content::RenderViewHostTestHarness(
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  void SetUp() override {
    RenderViewHostTestHarness::SetUp();
    HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry());
    settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
        &prefs_, false /* is_off_the_record */, false /* store_last_modified */,
        false /* restore_session*/, false /* should_record_metrics */);
    PageSpecificContentSettings::CreateForWebContents(
        web_contents(),
        std::make_unique<TestPageSpecificContentSettingsDelegate>(
            &prefs_, settings_map_.get()));
  }

  void TearDown() override {
    settings_map_->ShutdownOnUIThread();
    RenderViewHostTestHarness::TearDown();
  }

  HostContentSettingsMap* settings_map() { return settings_map_.get(); }
  PrefService* prefs() { return &prefs_; }

  content::WebContentsObserver* GetHandle() {
    return PageSpecificContentSettings::GetWebContentsObserverForTest(
        web_contents());
  }

  MockPageSpecificContentSettingsDelegate* InstallMockDelegate() {
    PageSpecificContentSettings::DeleteForWebContentsForTest(web_contents());
    PageSpecificContentSettings::CreateForWebContents(
        web_contents(),
        std::make_unique<
            testing::NiceMock<MockPageSpecificContentSettingsDelegate>>(
            prefs(), settings_map()));
    return static_cast<MockPageSpecificContentSettingsDelegate*>(
        PageSpecificContentSettings::GetDelegateForWebContents(web_contents()));
  }

 private:
  sync_preferences::TestingPrefServiceSyncable prefs_;
  scoped_refptr<HostContentSettingsMap> settings_map_;
};

TEST_F(PageSpecificContentSettingsTest, BlockedContent) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  // Check that after initializing, nothing is blocked.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::IMAGES));
#endif
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
  EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::SOUND));
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
  EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::MEDIASTREAM_MIC));
  EXPECT_FALSE(content_settings->IsContentBlocked(
      ContentSettingsType::MEDIASTREAM_CAMERA));

  // Set a cookie, block access to images, block mediastream access and block a
  // popup.
  GURL origin("http://google.com");
  std::unique_ptr<net::CanonicalCookie> cookie1(
      net::CanonicalCookie::CreateForTesting(origin, "A=B", base::Time::Now(),
                                             net::CookieSourceType::kOther));
  ASSERT_TRUE(cookie1);
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kChange,
                                  origin,
                                  origin,
                                  {{*cookie1}},
                                  false});
  content_settings = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  content_settings->OnContentBlocked(ContentSettingsType::IMAGES);
#endif
  content_settings->OnContentBlocked(ContentSettingsType::POPUPS);
  PageSpecificContentSettings::MicrophoneCameraState
      blocked_microphone_camera_state = {
          PageSpecificContentSettings::kMicrophoneAccessed,
          PageSpecificContentSettings::kMicrophoneBlocked,
          PageSpecificContentSettings::kCameraAccessed,
          PageSpecificContentSettings::kCameraBlocked};
  content_settings->OnMediaStreamPermissionSet(GURL("http://google.com"),
                                               blocked_microphone_camera_state);

  // Check that only the respective content types are affected.
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::IMAGES));
#endif
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
  EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::SOUND));
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
  EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
  EXPECT_TRUE(
      content_settings->IsContentBlocked(ContentSettingsType::MEDIASTREAM_MIC));
  EXPECT_TRUE(content_settings->IsContentBlocked(
      ContentSettingsType::MEDIASTREAM_CAMERA));
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kChange,
                                  origin,
                                  origin,
                                  {{*cookie1}},
                                  false});

  // Block a cookie.
  std::unique_ptr<net::CanonicalCookie> cookie2(
      net::CanonicalCookie::CreateForTesting(origin, "C=D", base::Time::Now(),
                                             net::CookieSourceType::kOther));
  ASSERT_TRUE(cookie2);
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kChange,
                                  origin,
                                  origin,
                                  {{*cookie2}},
                                  true});
  EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // Block a javascript during a navigation.
  // Create a pending navigation.
  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("http://google.com"), web_contents());
  simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  simulator->Start();
  GetHandle()->OnServiceWorkerAccessed(
      simulator->GetNavigationHandle(), GURL("http://google.com"),
      content::AllowServiceWorkerResult::FromPolicy(true, false));
  content_settings = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
  simulator->Commit();
  content_settings = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());

  // Block a javascript when page starts to start ServiceWorker.
  GetHandle()->OnServiceWorkerAccessed(
      web_contents()->GetPrimaryMainFrame(), GURL("http://google.com"),
      content::AllowServiceWorkerResult::FromPolicy(true, false));
  EXPECT_TRUE(
      content_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));

  // Reset blocked content settings.
  NavigateAndCommit(GURL("http://google.com"));
  content_settings = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::IMAGES));
#endif
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::JAVASCRIPT));
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
  EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::MEDIASTREAM_MIC));
  EXPECT_FALSE(content_settings->IsContentBlocked(
      ContentSettingsType::MEDIASTREAM_CAMERA));
}

TEST_F(PageSpecificContentSettingsTest, BlockedFileSystems) {
  NavigateAndCommit(GURL("http://google.com"));
  auto* rfh = web_contents()->GetPrimaryMainFrame();
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(rfh);
  auto google_storage_key = rfh->GetStorageKey();
  // Access a file system.
  PageSpecificContentSettings::StorageAccessed(
      StorageType::FILE_SYSTEM, rfh->GetGlobalId(), google_storage_key, false);
  EXPECT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // Block access to a file system.
  PageSpecificContentSettings::StorageAccessed(
      StorageType::FILE_SYSTEM, rfh->GetGlobalId(), google_storage_key, true);
  EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
}

TEST_F(PageSpecificContentSettingsTest, AllowedContent) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  // Test default settings.
  ASSERT_FALSE(content_settings->IsContentAllowed(ContentSettingsType::IMAGES));
  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::MEDIASTREAM_MIC));
  ASSERT_FALSE(content_settings->IsContentAllowed(
      ContentSettingsType::MEDIASTREAM_CAMERA));

  // Record a cookie.
  GURL origin("http://google.com");
  std::unique_ptr<net::CanonicalCookie> cookie1(
      net::CanonicalCookie::CreateForTesting(origin, "A=B", base::Time::Now(),
                                             net::CookieSourceType::kOther));
  ASSERT_TRUE(cookie1);
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kChange,
                                  origin,
                                  origin,
                                  {{*cookie1}},
                                  false});
  ASSERT_TRUE(content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // Record a blocked cookie.
  std::unique_ptr<net::CanonicalCookie> cookie2(
      net::CanonicalCookie::CreateForTesting(origin, "C=D", base::Time::Now(),
                                             net::CookieSourceType::kOther));
  ASSERT_TRUE(cookie2);
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kChange,
                                  origin,
                                  origin,
                                  {{*cookie2}},
                                  true});
  ASSERT_TRUE(content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
}

TEST_F(PageSpecificContentSettingsTest, AllowlistJavaScript) {
  const GURL url("http://google.com");
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();

  // PageSpecificContentSettingsDelegate::IsFrameAllowlistedForJavaScript() is
  // called once per navigation.
  EXPECT_CALL(*mock_delegate, IsFrameAllowlistedForJavaScript(::testing::_))
      .Times(2)
      .WillOnce(testing::Return(false))
      .WillOnce(testing::Return(true));

  content::NavigationHandleObserver observer(web_contents(), url);

  // Disable JavaScript. The secondary URL is ignored. This call is functionally
  // equivalent to setting `secondary_url` = wildcard.
  settings_map()->SetContentSettingDefaultScope(
      url, url, ContentSettingsType::JAVASCRIPT, CONTENT_SETTING_BLOCK);

  NavigateAndCommit(url);

  // The first mock call returns false for allowing JavaScript. The first
  // navigation should not allow JavaScript in the main frame.
  EXPECT_FALSE(observer.content_settings()->allow_script);

  // The second mock call returns true for allowing JavaScript. The second
  // navigation should allow JavaScript in the main frame.
  NavigateAndCommit(url);

  EXPECT_TRUE(observer.content_settings()->allow_script);
}

TEST_F(PageSpecificContentSettingsTest, InterestGroupJoin) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // Check that a blocking an interest join does not result in a blocked cookie
  // report.
  auto api_origin = url::Origin::Create(GURL("https://embedded.com"));
  content_settings->OnInterestGroupJoined(api_origin,
                                          /*blocked_by_policy=*/true);

  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // But that a successful join results in cookie access.
  content_settings->OnInterestGroupJoined(api_origin,
                                          /*blocked_by_policy=*/false);
  ASSERT_TRUE(content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
}

TEST_F(PageSpecificContentSettingsTest, BrowsingDataAccessed) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // Only some browsing data types should be reported as blocked cookies.
  auto origin = url::Origin::Create(GURL("https://embedded.com"));
  content_settings->OnBrowsingDataAccessed(
      origin, BrowsingDataModel::StorageType::kTrustTokens,
      /*blocked=*/true);

  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  auto storage_key = blink::StorageKey::CreateFirstParty(origin);
  content_settings->OnBrowsingDataAccessed(
      storage_key, BrowsingDataModel::StorageType::kLocalStorage,
      /*blocked=*/true);

  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));

  // But allowed accesses should be reported.
  content_settings->OnBrowsingDataAccessed(
      origin, BrowsingDataModel::StorageType::kTrustTokens,
      /*blocked=*/false);
  ASSERT_TRUE(content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
}

TEST_F(PageSpecificContentSettingsTest, EmptyCookieList) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
  GetHandle()->OnCookiesAccessed(
      web_contents()->GetPrimaryMainFrame(),
      {content::CookieAccessDetails::Type::kRead, GURL("http://google.com"),
       GURL("http://google.com"), net::CookieAccessResultList(), true});
  ASSERT_FALSE(
      content_settings->IsContentAllowed(ContentSettingsType::COOKIES));
  ASSERT_FALSE(
      content_settings->IsContentBlocked(ContentSettingsType::COOKIES));
}

TEST_F(PageSpecificContentSettingsTest, BlockedThirdPartyCookie) {
  NavigateAndCommit(GURL("https://google.com"));
  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());

  std::unique_ptr<net::CanonicalCookie> cookie(
      net::CanonicalCookie::CreateForTesting(
          GURL("https://google.com"),
          "CookieName=CookieValue;Secure;SameSite=None", base::Time::Now(),
          net::CookieSourceType::kOther));

  // 1P cookie should not be blocked.
  GetHandle()->OnCookiesAccessed(
      web_contents()->GetPrimaryMainFrame(),
      {content::CookieAccessDetails::Type::kRead,
       /*url=*/GURL("https://google.com"),
       /*first_party_url=*/GURL("https://google.com"),
       {{*cookie}},
       /*blocked_by_policy=*/true,
       /*is_ad_tagged=*/false,
       net::CookieSettingOverrides(),
       net::SiteForCookies::FromUrl(GURL("https://google.com"))});

  auto* blocked_data_model = pscs->blocked_browsing_data_model();
  size_t count = 0u;
  for (const auto& entry : *blocked_data_model) {
    if (entry.data_details->blocked_third_party) {
      ++count;
    }
  }
  EXPECT_EQ(0u, count);

  // 1P cookie in ABA embed should be blocked.
  GetHandle()->OnCookiesAccessed(
      web_contents()->GetPrimaryMainFrame(),
      {content::CookieAccessDetails::Type::kRead,
       /*url=*/GURL("https://google.com"),
       /*first_party_url=*/GURL("https://google.com"),
       {{*cookie}},
       /*blocked_by_policy=*/true,
       /*is_ad_tagged=*/false,
       net::CookieSettingOverrides(),
       net::SiteForCookies()});

  count = 0u;
  for (const auto& entry : *blocked_data_model) {
    if (entry.data_details->blocked_third_party) {
      ++count;
    }
  }
  EXPECT_EQ(1u, count);

  std::unique_ptr<net::CanonicalCookie> third_party_cookie(
      net::CanonicalCookie::CreateForTesting(
          GURL("https://example.com"),
          "CookieName=CookieValue;Secure;SameSite=None", base::Time::Now(),
          net::CookieSourceType::kOther));

  // 3P cookie should be blocked.
  GetHandle()->OnCookiesAccessed(
      web_contents()->GetPrimaryMainFrame(),
      {content::CookieAccessDetails::Type::kRead,
       /*url=*/GURL("https://google.com"),
       /*first_party_url=*/GURL("https://example.com"),
       {{*third_party_cookie}},
       /*blocked_by_policy=*/true,
       /*is_ad_tagged=*/false,
       net::CookieSettingOverrides(),
       net::SiteForCookies::FromUrl(GURL("https://example.com"))});

  count = 0u;
  for (const auto& entry : *blocked_data_model) {
    if (entry.data_details->blocked_third_party) {
      ++count;
    }
  }
  EXPECT_EQ(2u, count);
}

TEST_F(PageSpecificContentSettingsTest, SiteDataObserver) {
  NavigateAndCommit(GURL("http://google.com"));
  auto* rfh = web_contents()->GetPrimaryMainFrame();
  MockSiteDataObserver mock_observer(web_contents());
  EXPECT_CALL(mock_observer, OnSiteDataAccessed).Times(5);

  bool blocked_by_policy = false;
  GURL origin("http://google.com");
  std::unique_ptr<net::CanonicalCookie> cookie(
      net::CanonicalCookie::CreateForTesting(origin, "A=B", base::Time::Now(),
                                             net::CookieSourceType::kOther));
  ASSERT_TRUE(cookie);
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kChange,
                                  origin,
                                  origin,
                                  {{*cookie}},
                                  blocked_by_policy});

  net::CookieAccessResultList cookie_list;
  std::unique_ptr<net::CanonicalCookie> other_cookie(
      net::CanonicalCookie::CreateForTesting(
          GURL("http://google.com"), "CookieName=CookieValue",
          base::Time::Now(), net::CookieSourceType::kOther));
  ASSERT_TRUE(other_cookie);

  cookie_list.emplace_back(*other_cookie);
  GetHandle()->OnCookiesAccessed(
      rfh,
      {content::CookieAccessDetails::Type::kRead, GURL("http://google.com"),
       GURL("http://google.com"), cookie_list, blocked_by_policy});

  auto google_storage_key = rfh->GetStorageKey();
  PageSpecificContentSettings::StorageAccessed(
      StorageType::FILE_SYSTEM, rfh->GetGlobalId(), google_storage_key,
      blocked_by_policy);
  PageSpecificContentSettings::StorageAccessed(
      StorageType::INDEXED_DB, rfh->GetGlobalId(), google_storage_key,
      blocked_by_policy);
  PageSpecificContentSettings::StorageAccessed(
      StorageType::LOCAL_STORAGE, rfh->GetGlobalId(), google_storage_key,
      blocked_by_policy);
}

TEST_F(PageSpecificContentSettingsTest, BrowsingDataModelTrustToken) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();

  // Before Trust Token accesses, there should be no objects here.
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  const url::Origin origin = url::Origin::Create(GURL("http://google.com/"));
  const url::Origin issuer =
      url::Origin::Create(GURL("http://issuer.example/"));
  // Access a Trust Token.
  GetHandle()->OnTrustTokensAccessed(
      web_contents()->GetPrimaryMainFrame(),
      content::TrustTokenAccessDetails(
          origin, network::mojom::TrustTokenOperationType::kIssuance, issuer,
          false));

  EXPECT_EQ(1, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
}

TEST_F(PageSpecificContentSettingsTest,
       BrowsingDataModelTrustTokenPendingNavigation) {
  NavigateAndCommit(GURL("http://google.com"));

  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("http://other.com"), web_contents());
  simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  simulator->Start();

  const url::Origin origin = url::Origin::Create(GURL("http://google.com/"));
  const url::Origin issuer =
      url::Origin::Create(GURL("http://issuer.example/"));
  // Access a Trust Token.
  GetHandle()->OnTrustTokensAccessed(
      simulator->GetNavigationHandle(),
      content::TrustTokenAccessDetails(
          origin, network::mojom::TrustTokenOperationType::kIssuance, issuer,
          false));
  simulator->Commit();

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      simulator->GetFinalRenderFrameHost());
  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();

  // Before Trust Token accesses, there should be no objects here.
  EXPECT_EQ(1, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
}

TEST_F(PageSpecificContentSettingsTest, BrowsingDataModelSharedDictionary) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();
  auto* blocked_browsing_data_model = pscs->blocked_browsing_data_model();

  // Before Shared Dictionary accesses, there should be no objects here.
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*blocked_browsing_data_model));
  const url::Origin origin = url::Origin::Create(GURL("http://google.com/"));
  net::SharedDictionaryIsolationKey isolation_key(origin,
                                                  net::SchemefulSite(origin));
  // Access a Shared Dictionary.
  network::mojom::SharedDictionaryAccessDetailsPtr details =
      network::mojom::SharedDictionaryAccessDetails::New(
          network::mojom::SharedDictionaryAccessDetails::Type::kRead,
          GURL("http://test.example/target"), isolation_key,
          /*is_blocked=*/false);
  GetHandle()->OnSharedDictionaryAccessed(web_contents()->GetPrimaryMainFrame(),
                                          *details);

  EXPECT_EQ(1, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*blocked_browsing_data_model));
  ASSERT_EQ(1u, allowed_browsing_data_model->size());
  EXPECT_EQ("google.com",
            *std::get_if<std::string>(
                &*(*allowed_browsing_data_model->begin()).data_owner));
}

TEST_F(PageSpecificContentSettingsTest,
       BrowsingDataModelSharedDictionaryBlocked) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();
  auto* blocked_browsing_data_model = pscs->blocked_browsing_data_model();

  // Before Shared Dictionary accesses, there should be no objects here.
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*blocked_browsing_data_model));
  const url::Origin origin = url::Origin::Create(GURL("http://google.com/"));
  net::SharedDictionaryIsolationKey isolation_key(origin,
                                                  net::SchemefulSite(origin));
  // Blocked a Shared Dictionary access.
  network::mojom::SharedDictionaryAccessDetailsPtr details =
      network::mojom::SharedDictionaryAccessDetails::New(
          network::mojom::SharedDictionaryAccessDetails::Type::kRead,
          GURL("http://test.example/target"), isolation_key,
          /*is_blocked=*/true);
  GetHandle()->OnSharedDictionaryAccessed(web_contents()->GetPrimaryMainFrame(),
                                          *details);

  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  EXPECT_EQ(1, browsing_data::GetUniqueHostCount(*blocked_browsing_data_model));
  ASSERT_EQ(1u, blocked_browsing_data_model->size());
  EXPECT_EQ("google.com",
            *std::get_if<std::string>(
                &*(*blocked_browsing_data_model->begin()).data_owner));
}

TEST_F(PageSpecificContentSettingsTest,
       BrowsingDataModelSharedDictionaryPendingNavigation) {
  NavigateAndCommit(GURL("http://google.com"));

  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("http://other.com"), web_contents());
  simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  simulator->Start();

  // Access a Shared Dictionary.
  const url::Origin origin = url::Origin::Create(GURL("http://google.com/"));
  net::SharedDictionaryIsolationKey isolation_key(origin,
                                                  net::SchemefulSite(origin));
  network::mojom::SharedDictionaryAccessDetailsPtr details =
      network::mojom::SharedDictionaryAccessDetails::New(
          network::mojom::SharedDictionaryAccessDetails::Type::kRead,
          GURL("http://test.example/target"), isolation_key,
          /*is_blocked=*/false);
  GetHandle()->OnSharedDictionaryAccessed(simulator->GetNavigationHandle(),
                                          *details);
  simulator->Commit();

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      simulator->GetFinalRenderFrameHost());
  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();
  auto* blocked_browsing_data_model = pscs->blocked_browsing_data_model();

  EXPECT_EQ(1, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*blocked_browsing_data_model));
  ASSERT_EQ(1u, allowed_browsing_data_model->size());
  EXPECT_EQ("google.com",
            *std::get_if<std::string>(
                &*(*allowed_browsing_data_model->begin()).data_owner));
}

TEST_F(PageSpecificContentSettingsTest,
       BrowsingDataModelSharedDictionaryPendingNavigationBlocked) {
  NavigateAndCommit(GURL("http://google.com"));

  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("http://other.com"), web_contents());
  simulator->SetTransition(ui::PAGE_TRANSITION_LINK);
  simulator->Start();

  // Blocked a Shared Dictionary access.
  const url::Origin origin = url::Origin::Create(GURL("http://google.com/"));
  net::SharedDictionaryIsolationKey isolation_key(origin,
                                                  net::SchemefulSite(origin));
  network::mojom::SharedDictionaryAccessDetailsPtr details =
      network::mojom::SharedDictionaryAccessDetails::New(
          network::mojom::SharedDictionaryAccessDetails::Type::kRead,
          GURL("http://test.example/target"), isolation_key,
          /*is_blocked=*/true);
  GetHandle()->OnSharedDictionaryAccessed(simulator->GetNavigationHandle(),
                                          *details);
  simulator->Commit();

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      simulator->GetFinalRenderFrameHost());
  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();
  auto* blocked_browsing_data_model = pscs->blocked_browsing_data_model();

  EXPECT_EQ(0, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
  EXPECT_EQ(1, browsing_data::GetUniqueHostCount(*blocked_browsing_data_model));
  ASSERT_EQ(1u, blocked_browsing_data_model->size());
  EXPECT_EQ("google.com",
            *std::get_if<std::string>(
                &*(*blocked_browsing_data_model->begin()).data_owner));
}

#if !BUILDFLAG(IS_IOS)
TEST_F(PageSpecificContentSettingsTest,
       IndicatorChangedOnContentSettingChange) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  // First trigger OnContentBlocked.
  EXPECT_FALSE(content_settings->IsContentBlocked(
      ContentSettingsType::CLIPBOARD_READ_WRITE));
  content_settings->OnContentBlocked(ContentSettingsType::CLIPBOARD_READ_WRITE);
  EXPECT_TRUE(content_settings->IsContentBlocked(
      ContentSettingsType::CLIPBOARD_READ_WRITE));

  // Simulate the user modifying the setting.
  HostContentSettingsMap* map = settings_map();

  ContentSettingsPattern pattern =
      ContentSettingsPattern::FromURL(web_contents()->GetVisibleURL());

  map->SetWebsiteSettingCustomScope(pattern, ContentSettingsPattern::Wildcard(),
                                    ContentSettingsType::CLIPBOARD_READ_WRITE,
                                    base::Value(CONTENT_SETTING_ALLOW));

  // Now the indicator is set to allowed.
  EXPECT_TRUE(content_settings->IsContentAllowed(
      ContentSettingsType::CLIPBOARD_READ_WRITE));
  EXPECT_FALSE(content_settings->IsContentBlocked(
      ContentSettingsType::CLIPBOARD_READ_WRITE));

  // Simulate the user modifying the setting back to blocked.
  map->SetWebsiteSettingCustomScope(pattern, ContentSettingsPattern::Wildcard(),
                                    ContentSettingsType::CLIPBOARD_READ_WRITE,
                                    base::Value(CONTENT_SETTING_BLOCK));

  // Now the indicator is set to allowed.
  EXPECT_TRUE(content_settings->IsContentBlocked(
      ContentSettingsType::CLIPBOARD_READ_WRITE));
  EXPECT_FALSE(content_settings->IsContentAllowed(
      ContentSettingsType::CLIPBOARD_READ_WRITE));
}
#endif

TEST_F(PageSpecificContentSettingsTest, GeolocationHeaderAttachedToNavigation) {
  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://google.com"), web_contents());
  simulator->SetTransition(ui::PAGE_TRANSITION_GENERATED);
  simulator->Start();

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  EXPECT_FALSE(pscs->IsContentAllowed(ContentSettingsType::GEOLOCATION));

  PageSpecificContentSettings::GeolocationHeaderAttachedToNavigation(
      simulator->GetNavigationHandle());

  simulator->Commit();

  pscs = PageSpecificContentSettings::GetForFrame(
      simulator->GetFinalRenderFrameHost());
  ASSERT_TRUE(pscs);
  EXPECT_TRUE(pscs->IsContentAllowed(ContentSettingsType::GEOLOCATION));
}

TEST_F(PageSpecificContentSettingsTest,
       GeolocationHeaderRemovedFromNavigation) {
  std::unique_ptr<content::NavigationSimulator> simulator =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://google.com"), web_contents());
  simulator->SetTransition(ui::PAGE_TRANSITION_GENERATED);
  simulator->Start();

  PageSpecificContentSettings::GeolocationHeaderAttachedToNavigation(
      simulator->GetNavigationHandle());
  PageSpecificContentSettings::GeolocationHeaderRemovedFromNavigation(
      simulator->GetNavigationHandle());

  simulator->Commit();

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      simulator->GetFinalRenderFrameHost());
  ASSERT_TRUE(pscs);
  EXPECT_FALSE(pscs->IsContentAllowed(ContentSettingsType::GEOLOCATION));
}

TEST_F(PageSpecificContentSettingsTest, AllowedSitesCountedFromBothModels) {
  // Populate containers with hosts.
  bool blocked_by_policy = false;
  auto googleURL = GURL("http://google.com");
  auto exampleURL = GURL("https://example.com");
  auto cookie1 = net::CanonicalCookie::CreateForTesting(
      googleURL, "k1=v", base::Time::Now(), net::CookieSourceType::kOther);
  auto cookie2 = net::CanonicalCookie::CreateForTesting(
      exampleURL, "k2=v", base::Time::Now(), net::CookieSourceType::kOther);
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kRead,
                                  googleURL,
                                  googleURL,
                                  {{*cookie1}},
                                  blocked_by_policy});
  GetHandle()->OnCookiesAccessed(web_contents()->GetPrimaryMainFrame(),
                                 {content::CookieAccessDetails::Type::kRead,
                                  exampleURL,
                                  exampleURL,
                                  {{*cookie2}},
                                  blocked_by_policy});

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());

  auto* allowed_browsing_data_model = pscs->allowed_browsing_data_model();
  allowed_browsing_data_model->AddBrowsingData(
      url::Origin::Create(exampleURL),
      BrowsingDataModel::StorageType::kTrustTokens, /*storage_size=*/0);

  // Verify the size is counted without duplication of hosts.
  EXPECT_EQ(2, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
}

TEST_F(PageSpecificContentSettingsTest, BrowsingDataModelStorageAccess) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());
  bool blocked_by_policy = false;

  content_settings->OnBrowsingDataAccessed(
      CreateUnpartitionedStorageKey(GURL("https://www.google.com")),
      BrowsingDataModel::StorageType::kQuotaStorage, blocked_by_policy);
  content_settings->OnBrowsingDataAccessed(
      CreateUnpartitionedStorageKey(GURL("http://example.com")),
      BrowsingDataModel::StorageType::kLocalStorage, blocked_by_policy);
  content_settings->OnBrowsingDataAccessed(
      CreateUnpartitionedStorageKey(GURL("https://www.youtube.com")),
      BrowsingDataModel::StorageType::kSharedStorage, blocked_by_policy);

  const auto* allowed_browsing_data_model =
      content_settings->allowed_browsing_data_model();

  EXPECT_EQ(3, browsing_data::GetUniqueHostCount(*allowed_browsing_data_model));
}

class PageSpecificContentSettingsWithPrerenderTest
    : public PageSpecificContentSettingsTest {
 public:
  PageSpecificContentSettingsWithPrerenderTest() = default;

  content::RenderFrameHost* AddPrerender(const GURL& prerender_url) {
    web_contents_delegate_ =
        std::make_unique<content::test::ScopedPrerenderWebContentsDelegate>(
            *web_contents());
    content::RenderFrameHost* prerender_frame =
        content::WebContentsTester::For(web_contents())
            ->AddPrerenderAndCommitNavigation(prerender_url);
    DCHECK(prerender_frame);
    DCHECK_EQ(prerender_frame->GetLifecycleState(),
              content::RenderFrameHost::LifecycleState::kPrerendering);
    DCHECK_EQ(prerender_frame->GetLastCommittedURL(), prerender_url);
    return prerender_frame;
  }

 private:
  content::test::ScopedPrerenderFeatureList prerender_feature_list_;
  std::unique_ptr<content::test::ScopedPrerenderWebContentsDelegate>
      web_contents_delegate_;
};

TEST_F(PageSpecificContentSettingsWithPrerenderTest, SiteDataAccessed) {
  NavigateAndCommit(GURL("http://google.com"));
  const GURL& prerender_url = GURL("http://google.com/foo");
  content::RenderFrameHost* prerender_frame = AddPrerender(prerender_url);
  PageSpecificContentSettings* pscs =
      PageSpecificContentSettings::GetForFrame(prerender_frame);
  ASSERT_NE(pscs, nullptr);

  // Simulate cookie access.
  {
    MockSiteDataObserver mock_observer(web_contents());
    // OnSiteDataAccessed should not be called for prerendering page.
    EXPECT_CALL(mock_observer, OnSiteDataAccessed).Times(0);
    // Set a cookie, block access to images, block mediastream access and block
    // a popup.
    GURL origin("http://google.com");
    std::unique_ptr<net::CanonicalCookie> cookie1(
        net::CanonicalCookie::CreateForTesting(origin, "A=B", base::Time::Now(),
                                               net::CookieSourceType::kOther));
    ASSERT_TRUE(cookie1);
    pscs->OnCookiesAccessed({content::CookieAccessDetails::Type::kChange,
                             origin,
                             origin,
                             {{*cookie1}},
                             false});
  }
  // Activate prerendering page.
  {
    MockSiteDataObserver mock_observer(web_contents());
    // OnSiteDataAccessed should be called after page is activated.
    EXPECT_CALL(mock_observer, OnSiteDataAccessed).Times(1);
    std::unique_ptr<content::NavigationSimulator> navigation =
        content::NavigationSimulator::CreateRendererInitiated(
            prerender_url, web_contents()->GetPrimaryMainFrame());
    // TODO(crbug.com/40170513): Investigate how default referrer value
    // is set and update here accordingly.
    navigation->SetReferrer(blink::mojom::Referrer::New(
        web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL(),
        network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
    navigation->Commit();
  }
}

TEST_F(PageSpecificContentSettingsWithPrerenderTest,
       DelegateUpdatesSentAfterActivation) {
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();
  NavigateAndCommit(GURL("http://google.com"));
  const GURL& prerender_url = GURL("http://google.com/foo");
  content::RenderFrameHost* prerender_frame = AddPrerender(prerender_url);
  PageSpecificContentSettings* pscs =
      PageSpecificContentSettings::GetForFrame(prerender_frame);
  ASSERT_NE(pscs, nullptr);

  EXPECT_CALL(*mock_delegate, OnContentAllowed).Times(0);
  EXPECT_CALL(*mock_delegate, OnContentBlocked).Times(0);

  const GURL url = GURL("http://google.com");
  auto cookie = net::CanonicalCookie::CreateForTesting(
      url, "k=v", base::Time::Now(), net::CookieSourceType::kOther);
  pscs->OnCookiesAccessed({content::CookieAccessDetails::Type::kRead,
                           url,
                           url,
                           {{*cookie}},
                           /*blocked_by_policy=*/false});
  PageSpecificContentSettings::StorageAccessed(StorageType::INDEXED_DB,
                                               prerender_frame->GetGlobalId(),
                                               prerender_frame->GetStorageKey(),
                                               /*blocked_by_policy=*/true);

  EXPECT_CALL(*mock_delegate, OnContentAllowed(ContentSettingsType::COOKIES))
      .Times(1);
  EXPECT_CALL(*mock_delegate, OnContentBlocked(ContentSettingsType::COOKIES))
      .Times(1);
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateRendererInitiated(
          prerender_url, web_contents()->GetPrimaryMainFrame());
  navigation->SetReferrer(blink::mojom::Referrer::New(
      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL(),
      network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
  navigation->Commit();
}

TEST_F(PageSpecificContentSettingsWithPrerenderTest,
       UpdateLocationBarAfterActivation) {
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();
  NavigateAndCommit(GURL("http://google.com"));
  const GURL& prerender_url = GURL("http://google.com/foo");
  content::RenderFrameHost* prerender_frame = AddPrerender(prerender_url);
  PageSpecificContentSettings* pscs =
      PageSpecificContentSettings::GetForFrame(prerender_frame);
  ASSERT_NE(pscs, nullptr);

  EXPECT_CALL(*mock_delegate, UpdateLocationBar()).Times(0);
  pscs->OnContentBlocked(ContentSettingsType::JAVASCRIPT);

  EXPECT_CALL(*mock_delegate, UpdateLocationBar()).Times(1);
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateRendererInitiated(
          prerender_url, web_contents()->GetPrimaryMainFrame());
  navigation->Commit();
}

TEST_F(PageSpecificContentSettingsWithPrerenderTest, ContentAllowedAndBlocked) {
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();
  NavigateAndCommit(GURL("http://google.com"));
  const GURL& prerender_url = GURL("http://google.com/foo");
  content::RenderFrameHost* prerender_frame = AddPrerender(prerender_url);
  PageSpecificContentSettings* pscs =
      PageSpecificContentSettings::GetForFrame(prerender_frame);
  ASSERT_NE(pscs, nullptr);

  EXPECT_CALL(*mock_delegate, OnContentAllowed).Times(0);
  EXPECT_CALL(*mock_delegate, OnContentBlocked).Times(0);
  pscs->OnContentBlocked(ContentSettingsType::JAVASCRIPT);
  pscs->OnContentAllowed(ContentSettingsType::COOKIES);

  EXPECT_CALL(*mock_delegate, OnContentBlocked(ContentSettingsType::JAVASCRIPT))
      .Times(1);
  EXPECT_CALL(*mock_delegate, OnContentAllowed(ContentSettingsType::COOKIES))
      .Times(1);
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateRendererInitiated(
          prerender_url, web_contents()->GetPrimaryMainFrame());
  // TODO(crbug.com/40170513): Investigate how default referrer value is
  // set and update here accordingly.
  navigation->SetReferrer(blink::mojom::Referrer::New(
      web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL(),
      network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin));
  navigation->Commit();
}

class PageSpecificContentSettingsWithFencedFrameTest
    : public PageSpecificContentSettingsTest {
 public:
  PageSpecificContentSettingsWithFencedFrameTest() {
    scoped_feature_list_.InitAndEnableFeatureWithParameters(
        blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
  }
  ~PageSpecificContentSettingsWithFencedFrameTest() override = default;

  content::RenderFrameHost* CreateFencedFrame(const GURL& url) {
    content::RenderFrameHost* fenced_frame_root =
        content::RenderFrameHostTester::For(
            web_contents()->GetPrimaryMainFrame())
            ->AppendFencedFrame();
    std::unique_ptr<content::NavigationSimulator> navigation_simulator =
        content::NavigationSimulator::CreateRendererInitiated(
            url, fenced_frame_root);
    navigation_simulator->Commit();
    return navigation_simulator->GetFinalRenderFrameHost();
  }

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

TEST_F(PageSpecificContentSettingsWithFencedFrameTest, SiteDataAccessed) {
  NavigateAndCommit(GURL("http://google.com"));
  const GURL& fenced_frame_url = GURL("http://foo.com");
  content::RenderFrameHost* fenced_frame_root =
      CreateFencedFrame(fenced_frame_url);
  PageSpecificContentSettings* ff_pscs =
      PageSpecificContentSettings::GetForFrame(fenced_frame_root);
  ASSERT_NE(ff_pscs, nullptr);

  // Simulate cookie access in fenced frame.
  {
    MockSiteDataObserver mock_observer(web_contents());
    EXPECT_CALL(mock_observer, OnSiteDataAccessed).Times(1);
    // Set a cookie, block access to images, block mediastream access and block
    // a popup.
    GURL origin("http://google.com");
    std::unique_ptr<net::CanonicalCookie> cookie1(
        net::CanonicalCookie::CreateForTesting(origin, "A=B", base::Time::Now(),
                                               net::CookieSourceType::kOther));
    ASSERT_TRUE(cookie1);
    ff_pscs->OnCookiesAccessed({content::CookieAccessDetails::Type::kChange,
                                origin,
                                origin,
                                {{*cookie1}},
                                false});
  }
}

TEST_F(PageSpecificContentSettingsWithFencedFrameTest, DelegateUpdatesSent) {
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();

  const GURL main_url("http://google.com");
  const GURL& ff_url = GURL("http://foo.com");
  const url::Origin& ff_origin = url::Origin::Create(ff_url);

  NavigateAndCommit(main_url);
  content::RenderFrameHost* fenced_frame_root = CreateFencedFrame(ff_url);
  PageSpecificContentSettings* ff_pscs =
      PageSpecificContentSettings::GetForFrame(fenced_frame_root);
  ASSERT_NE(ff_pscs, nullptr);

  EXPECT_CALL(*mock_delegate, OnContentAllowed(ContentSettingsType::COOKIES))
      .Times(1);
  EXPECT_CALL(*mock_delegate, OnContentBlocked(ContentSettingsType::COOKIES))
      .Times(1);

  auto cookie = net::CanonicalCookie::CreateForTesting(
      ff_url, "k=v", base::Time::Now(), net::CookieSourceType::kOther);
  ff_pscs->OnCookiesAccessed({content::CookieAccessDetails::Type::kRead,
                              ff_url,
                              ff_url,
                              {{*cookie}},
                              /*blocked_by_policy=*/false});
  PageSpecificContentSettings::StorageAccessed(
      StorageType::INDEXED_DB, fenced_frame_root->GetGlobalId(),
      fenced_frame_root->GetStorageKey(),
      /*blocked_by_policy=*/true);
}

TEST_F(PageSpecificContentSettingsWithFencedFrameTest,
       ContentAllowedAndBlocked) {
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();
  NavigateAndCommit(GURL("http://google.com"));
  const GURL& fenced_frame_url = GURL("http://foo.com");
  content::RenderFrameHost* fenced_frame_root =
      CreateFencedFrame(fenced_frame_url);
  PageSpecificContentSettings* ff_pscs =
      PageSpecificContentSettings::GetForFrame(fenced_frame_root);
  ASSERT_NE(ff_pscs, nullptr);

  EXPECT_CALL(*mock_delegate, OnContentAllowed(ContentSettingsType::COOKIES))
      .Times(1);
  EXPECT_CALL(*mock_delegate, OnContentBlocked(ContentSettingsType::JAVASCRIPT))
      .Times(1);

  ff_pscs->OnContentBlocked(ContentSettingsType::JAVASCRIPT);
  ff_pscs->OnContentAllowed(ContentSettingsType::COOKIES);
}

TEST_F(PageSpecificContentSettingsTest,
       MediaIndicatorsBlockedDoNotOverrideInUse) {
  base::test::ScopedFeatureList scoped_feature_list_;
  scoped_feature_list_.InitAndEnableFeature(
      content_settings::features::kLeftHandSideActivityIndicators);

  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  pscs->OnMediaStreamPermissionSet(
      web_contents()->GetLastCommittedURL(),
      {PageSpecificContentSettings::kCameraAccessed});

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  pscs->OnMediaStreamPermissionSet(
      web_contents()->GetLastCommittedURL(),
      {PageSpecificContentSettings::kMicrophoneAccessed,
       PageSpecificContentSettings::kMicrophoneBlocked});

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().HasAny(
      {PageSpecificContentSettings::kMicrophoneAccessed,
       PageSpecificContentSettings::kMicrophoneBlocked}));
}

TEST_F(PageSpecificContentSettingsTest, MediaIndicatorsMinHoldDurationDelay) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  pscs->set_media_stream_access_origin_for_testing(
      web_contents()->GetLastCommittedURL());

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, true);

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, false);

  // `kCameraAccessed` is true because of min hold duration.
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  // Min hold duration equals to 5 seconds.
  task_environment()->AdvanceClock(base::Seconds(2));
  base::RunLoop().RunUntilIdle();

  // `kCameraAccessed` is still true because only 2 seconds passed.
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  task_environment()->AdvanceClock(base::Seconds(4));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
}

// Tests that if media indicator display time almost equals to the min hold
// duration, a delay should be not less than 1 second.
TEST_F(PageSpecificContentSettingsTest, AlmostExpiredMinHoldDurationDelay) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  pscs->set_media_stream_access_origin_for_testing(
      web_contents()->GetLastCommittedURL());

  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, true);

  task_environment()->AdvanceClock(base::Milliseconds(4800));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, false);

  //  `kCameraAccessed` is true because of 1 second delay.
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  task_environment()->AdvanceClock(base::Milliseconds(800));
  base::RunLoop().RunUntilIdle();

  //  `kCameraAccessed` is true because waited only 800 ms.
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  task_environment()->AdvanceClock(base::Milliseconds(300));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
}

TEST_F(PageSpecificContentSettingsTest,
       MediaIndicatorsHoldAfterUseDurationDelay) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  pscs->set_media_stream_access_origin_for_testing(
      web_contents()->GetLastCommittedURL());

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, true);

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  // Min hold duration equals to 5 seconds.
  task_environment()->AdvanceClock(base::Seconds(6));
  base::RunLoop().RunUntilIdle();

  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, false);

  // `kCameraAccessed` is true because of hold after use duration.
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  // Hold after use duration equals to 1 seconds.
  task_environment()->AdvanceClock(base::Seconds(2));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
}

TEST_F(PageSpecificContentSettingsTest,
       MediaIndicatorsReenableCameraWhileMinHoldDurationDelay) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  pscs->set_media_stream_access_origin_for_testing(
      web_contents()->GetLastCommittedURL());

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, true);

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, false);

  // `kCameraAccessed` is true because of the min hold duration.
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  // Reenabling a camera indicator
  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, true);

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));

  task_environment()->AdvanceClock(base::Seconds(6));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kCameraAccessed));
}

// Tests that `PageSpecificContentSettings` will return locally saved last used
// time if it exists.
TEST_F(PageSpecificContentSettingsTest, GetLastUsedTimeLocalTimeTest) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  base::Time time = base::Time::Now() - base::Seconds(30);

  pscs->set_last_used_time_for_testing(ContentSettingsType::MEDIASTREAM_CAMERA,
                                       time);

  EXPECT_EQ(time,
            pscs->GetLastUsedTime(ContentSettingsType::MEDIASTREAM_CAMERA));
}

TEST_F(PageSpecificContentSettingsTest, GetLastUsedReturnsDefaultTimeTest) {
  NavigateAndCommit(GURL("http://google.com"));

  HostContentSettingsMap* map = settings_map();

  const GURL& url = web_contents()->GetLastCommittedURL();
  map->SetContentSettingDefaultScope(
      url, url, ContentSettingsType::MEDIASTREAM_CAMERA, CONTENT_SETTING_ALLOW);

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  EXPECT_EQ(base::Time(),
            pscs->GetLastUsedTime(ContentSettingsType::MEDIASTREAM_CAMERA));
}

TEST_F(PageSpecificContentSettingsTest, GetLastUsedReturnCorrectTimeTest) {
  NavigateAndCommit(GURL("http://google.com"));

  HostContentSettingsMap* map = settings_map();

  const GURL& url = web_contents()->GetLastCommittedURL();
  map->SetContentSettingDefaultScope(
      url, url, ContentSettingsType::MEDIASTREAM_CAMERA, CONTENT_SETTING_ALLOW);

  base::Time time = base::Time::Now() - base::Hours(20);
  map->UpdateLastUsedTime(url, url, ContentSettingsType::MEDIASTREAM_CAMERA,
                          time);

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  EXPECT_EQ(time,
            pscs->GetLastUsedTime(ContentSettingsType::MEDIASTREAM_CAMERA));
}

// Tests that a permission blocked indicator is visible only for 60 seconds.
TEST_F(PageSpecificContentSettingsTest, MediaBlockedIndicatorsDismissDelay) {
  base::test::ScopedFeatureList scoped_feature_list_;
  scoped_feature_list_.InitWithFeatures(
      {}, {content_settings::features::kLeftHandSideActivityIndicators});

  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  EXPECT_FALSE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));

  PageSpecificContentSettings::MicrophoneCameraState
      blocked_microphone_camera_state = {
          PageSpecificContentSettings::kMicrophoneAccessed,
          PageSpecificContentSettings::kMicrophoneBlocked};
  pscs->OnMediaStreamPermissionSet(GURL("http://google.com"),
                                   blocked_microphone_camera_state);

  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));

  task_environment()->AdvanceClock(base::Seconds(57));
  base::RunLoop().RunUntilIdle();

  // The timer is still there because a delay is 60 seconds.
  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));

  task_environment()->AdvanceClock(base::Seconds(4));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));
}

// Tests that a permission indicator will not be dismissed by a timer if was
// opened.
TEST_F(PageSpecificContentSettingsTest,
       MediaIndicatorsDoNotDismissIfOpenedDelay) {
  base::test::ScopedFeatureList scoped_feature_list_;
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  PageSpecificContentSettings::MicrophoneCameraState
      blocked_microphone_camera_state = {
          PageSpecificContentSettings::kMicrophoneAccessed,
          PageSpecificContentSettings::kMicrophoneBlocked};
  pscs->OnMediaStreamPermissionSet(GURL("http://google.com"),
                                   blocked_microphone_camera_state);

  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));
  // 60 secodns timer is on.
  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing()
                  [ContentSettingsType::MEDIASTREAM_MIC]
                      .IsRunning());

  pscs->OnActivityIndicatorBubbleOpened(ContentSettingsType::MEDIASTREAM_MIC);

  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));
  // A mic timer was stopped.
  EXPECT_FALSE(pscs->get_media_blocked_indicator_timer_for_testing()
                   [ContentSettingsType::MEDIASTREAM_MIC]
                       .IsRunning());

  // A blockage indicator timer was stopped because an activity indicator popup
  // bubble was opened.
  task_environment()->AdvanceClock(base::Seconds(65));
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kMicrophoneAccessed));
  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kMicrophoneBlocked));

  // An indicator popup bubble is closed.
  pscs->OnActivityIndicatorBubbleClosed(ContentSettingsType::MEDIASTREAM_MIC);

  // A mic timer was restarted.
  EXPECT_TRUE(pscs->get_media_blocked_indicator_timer_for_testing()
                  [ContentSettingsType::MEDIASTREAM_MIC]
                      .IsRunning());

  task_environment()->AdvanceClock(base::Seconds(61));
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(pscs->get_media_blocked_indicator_timer_for_testing().contains(
      ContentSettingsType::MEDIASTREAM_MIC));
  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kMicrophoneAccessed));
  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kMicrophoneBlocked));
}

// Tests that a permission blocked state is reset after media started to be
// used.
TEST_F(PageSpecificContentSettingsTest, MediaBlockedStateIsResetIfMediaUsed) {
  base::test::ScopedFeatureList scoped_feature_list_;
  scoped_feature_list_.InitAndEnableFeature(
      content_settings::features::kLeftHandSideActivityIndicators);

  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  PageSpecificContentSettings::MicrophoneCameraState
      blocked_microphone_camera_state = {
          PageSpecificContentSettings::kMicrophoneAccessed,
          PageSpecificContentSettings::kMicrophoneBlocked};
  pscs->OnMediaStreamPermissionSet(GURL("http://google.com"),
                                   blocked_microphone_camera_state);

  EXPECT_TRUE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kMicrophoneBlocked));

  // Camera is capturing, it should reset blocked microphone state.
  pscs->OnCapturingStateChanged(ContentSettingsType::MEDIASTREAM_CAMERA, true);

  EXPECT_FALSE(pscs->GetMicrophoneCameraState().Has(
      PageSpecificContentSettings::kMicrophoneBlocked));
}

// The only currently implemented object-based type is for now ChromeOS only
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(PageSpecificContentSettingsTest, ObjectBasedInUseIndicator) {
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  // Initially, no indicator should be visible and `IsInUse` should be false.
  EXPECT_FALSE(pscs->ShouldShowDeviceInUseIndicator(
      ContentSettingsType::SMART_CARD_GUARD));
  EXPECT_FALSE(pscs->IsInUse(ContentSettingsType::SMART_CARD_GUARD));

  // When the first connection is used, indicator should start appearing and
  // `IsInUse` should return `true`.
  pscs->OnDeviceUsed(ContentSettingsType::SMART_CARD_GUARD);
  EXPECT_TRUE(pscs->ShouldShowDeviceInUseIndicator(
      ContentSettingsType::SMART_CARD_GUARD));
  EXPECT_TRUE(pscs->IsInUse(ContentSettingsType::SMART_CARD_GUARD));

  // When the last connection closes, the indicator should still be visible
  // initially, even though `IsInUse` should be false already...
  pscs->OnLastDeviceConnectionLost(ContentSettingsType::SMART_CARD_GUARD);
  EXPECT_TRUE(pscs->ShouldShowDeviceInUseIndicator(
      ContentSettingsType::SMART_CARD_GUARD));
  EXPECT_FALSE(pscs->IsInUse(ContentSettingsType::SMART_CARD_GUARD));

  // ...but 15 seconds later it should disappear.
  task_environment()->AdvanceClock(base::Seconds(15));
  EXPECT_FALSE(pscs->ShouldShowDeviceInUseIndicator(
      ContentSettingsType::SMART_CARD_GUARD));
}
#endif

#if BUILDFLAG(IS_WIN)
TEST_F(PageSpecificContentSettingsTest, ProtectedMediaIdentifier) {
  MockPageSpecificContentSettingsDelegate* mock_delegate =
      InstallMockDelegate();
  NavigateAndCommit(GURL("http://google.com"));

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  ASSERT_NE(pscs, nullptr);

  // Allowed
  EXPECT_CALL(*mock_delegate,
              OnContentAllowed(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER))
      .Times(1);
  pscs->OnProtectedMediaIdentifierPermissionSet(
      web_contents()->GetLastCommittedURL(), /*allowed=*/true);

  // Blocked
  EXPECT_CALL(*mock_delegate,
              OnContentBlocked(ContentSettingsType::PROTECTED_MEDIA_IDENTIFIER))
      .Times(1);
  pscs->OnProtectedMediaIdentifierPermissionSet(
      web_contents()->GetLastCommittedURL(), /*allowed=*/false);
}
#endif  // BUILDFLAG(IS_WIN)

class PageSpecificContentSettingsTestWithApproximateLocation
    : public PageSpecificContentSettingsTest {
 private:
  base::test::ScopedFeatureList enable_approximate_location_{
      features::kApproximateGeolocationPermission};
};

TEST_F(PageSpecificContentSettingsTestWithApproximateLocation,
       GeolocationWithOptions) {
  GURL url("https://example.com");
  NavigateAndCommit(url);

  PageSpecificContentSettings* pscs = PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());

  settings_map()->SetPermissionSettingDefaultScope(
      url, url, ContentSettingsType::GEOLOCATION_WITH_OPTIONS,
      GeolocationSetting{.approximate = PermissionOption::kAllowed,
                         .precise = PermissionOption::kAllowed});
  pscs->OnContentAllowed(ContentSettingsType::GEOLOCATION_WITH_OPTIONS);
  EXPECT_TRUE(
      pscs->IsContentAllowed(ContentSettingsType::GEOLOCATION_WITH_OPTIONS));
  EXPECT_FALSE(
      pscs->IsContentBlocked(ContentSettingsType::GEOLOCATION_WITH_OPTIONS));

  // Denying only precise location shouldn't change anything.
  settings_map()->SetPermissionSettingDefaultScope(
      url, url, ContentSettingsType::GEOLOCATION_WITH_OPTIONS,
      GeolocationSetting{.approximate = PermissionOption::kAllowed,
                         .precise = PermissionOption::kDenied});
  EXPECT_TRUE(
      pscs->IsContentAllowed(ContentSettingsType::GEOLOCATION_WITH_OPTIONS));
  EXPECT_FALSE(
      pscs->IsContentBlocked(ContentSettingsType::GEOLOCATION_WITH_OPTIONS));

  // Denying also approximate location should update the blocked status.
  settings_map()->SetPermissionSettingDefaultScope(
      url, url, ContentSettingsType::GEOLOCATION_WITH_OPTIONS,
      GeolocationSetting{.approximate = PermissionOption::kDenied,
                         .precise = PermissionOption::kDenied});
  EXPECT_FALSE(
      pscs->IsContentAllowed(ContentSettingsType::GEOLOCATION_WITH_OPTIONS));
  EXPECT_TRUE(
      pscs->IsContentBlocked(ContentSettingsType::GEOLOCATION_WITH_OPTIONS));
}

class PageSpecificContentSettingsIframeTest
    : public PageSpecificContentSettingsTest {
 public:
  PageSpecificContentSettingsIframeTest() = default;

  void SetUp() override { PageSpecificContentSettingsTest::SetUp(); }

  // Navigates both the parent and child frames, and then gets content settings
  // from the child frame.
  blink::mojom::RendererContentSettingsPtr NavigateAndGetContentSettings(
      GURL parent_url,
      GURL child_url) {
    NavigateAndCommit(parent_url);

    content::NavigationHandleObserver observer(web_contents(), child_url);
    content::RenderFrameHost* child_rfh =
        content::RenderFrameHostTester::For(main_rfh())->AppendChild("child");
    auto simulator = content::NavigationSimulator::CreateRendererInitiated(
        child_url, child_rfh);
    simulator->SetTransition(ui::PAGE_TRANSITION_MANUAL_SUBFRAME);
    simulator->Commit();
    return observer.content_settings()->Clone();
  }
};

// Tests that the content settings are correctly set if a secondary url is
// blocked.
TEST_F(PageSpecificContentSettingsIframeTest, SecondaryUrlBlocked) {
  GURL parent_url("https://parent.com");
  GURL child_url("https://child.com");

  settings_map()->SetContentSettingDefaultScope(parent_url, child_url,
                                                ContentSettingsType::JAVASCRIPT,
                                                CONTENT_SETTING_BLOCK);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_FALSE(content_settings->allow_script);
}

// Tests that the content settings are correctly set if an unrelated secondary
// url is blocked.
TEST_F(PageSpecificContentSettingsIframeTest, UnrelatedSecondaryUrlBlocked) {
  GURL other_url("https://other.com");
  GURL parent_url("https://parent.com");
  GURL child_url("https://child.com");

  settings_map()->SetContentSettingDefaultScope(other_url, child_url,
                                                ContentSettingsType::JAVASCRIPT,
                                                CONTENT_SETTING_BLOCK);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_TRUE(content_settings->allow_script);
}

// Tests that the image content settings are correctly set if the primary and
// secondary urls are identical.
TEST_F(PageSpecificContentSettingsIframeTest, PrimarySecondaryIdentical) {
  GURL parent_url("https://parent.com");
  GURL child_url = parent_url;
  GURL other_url("https://other.com");

  // All content settings that are sent to the renderer are top-origin scoped.
  // Secondary_url is ignored. This call is functionally equivalent to setting
  // secondary_url = wildcard.
  settings_map()->SetContentSettingDefaultScope(parent_url, other_url,
                                                ContentSettingsType::JAVASCRIPT,
                                                CONTENT_SETTING_BLOCK);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_FALSE(content_settings->allow_script);
}

#if !BUILDFLAG(IS_IOS)
// Tests that the image content settings are correctly set if a secondary url is
// blocked.
TEST_F(PageSpecificContentSettingsIframeTest, SecondaryUrlBlockedForImages) {
  GURL parent_url("https://parent.com");
  GURL child_url("https://child.com");

  settings_map()->SetContentSettingDefaultScope(parent_url, child_url,
                                                ContentSettingsType::IMAGES,
                                                CONTENT_SETTING_BLOCK);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_FALSE(content_settings->allow_image);
}

// Tests that the image content settings are correctly set if an unrelated
// secondary url is blocked.
TEST_F(PageSpecificContentSettingsIframeTest,
       UnrelatedSecondaryUrlBlockedForImages) {
  GURL other_url("https://other.com");
  GURL parent_url("https://parent.com");
  GURL child_url("https://child.com");

  settings_map()->SetContentSettingDefaultScope(
      other_url, child_url, ContentSettingsType::IMAGES, CONTENT_SETTING_BLOCK);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_TRUE(content_settings->allow_image);
}

// Tests that the content settings are correctly set if the primary and
// secondary urls are identical.
TEST_F(PageSpecificContentSettingsIframeTest,
       PrimarySecondaryIdenticalForImages) {
  GURL parent_url("https://parent.com");
  GURL child_url = parent_url;
  GURL other_url("https://other.com");

  // All content settings that are sent to the renderer are top-origin scoped.
  // Secondary_url is ignored. This call is functionally equivalent to setting
  // secondary_url = wildcard.
  settings_map()->SetContentSettingDefaultScope(parent_url, other_url,
                                                ContentSettingsType::IMAGES,
                                                CONTENT_SETTING_BLOCK);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_FALSE(content_settings->allow_image);
}

// Tests that the mixed content settings are correctly set if a secondary
// url is allowed.
TEST_F(PageSpecificContentSettingsIframeTest,
       SecondaryUrlBlockedForMixedScript) {
  GURL parent_url("https://parent.com");
  GURL child_url("https://child.com");

  settings_map()->SetContentSettingDefaultScope(
      parent_url, child_url, ContentSettingsType::MIXEDSCRIPT,
      CONTENT_SETTING_ALLOW);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_TRUE(content_settings->allow_mixed_content);
}

// Tests that the mixed content settings are correctly set if an unrelated
// secondary url is allowed.
TEST_F(PageSpecificContentSettingsIframeTest,
       UnrelatedSecondaryUrlBlockedForMixedScript) {
  GURL other_url("https://other.com");
  GURL parent_url("https://parent.com");
  GURL child_url("https://child.com");

  settings_map()->SetContentSettingDefaultScope(
      other_url, child_url, ContentSettingsType::MIXEDSCRIPT,
      CONTENT_SETTING_ALLOW);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_FALSE(content_settings->allow_mixed_content);
}

// Tests that the content settings are correctly set if the primary and
// secondary urls are identical.
TEST_F(PageSpecificContentSettingsIframeTest,
       PrimarySecondaryIdenticalForMixedScript) {
  GURL parent_url("https://parent.com");
  GURL child_url = parent_url;
  GURL other_url("https://other.com");

  // All content settings that are sent to the renderer are top-origin scoped.
  // Secondary_url is ignored. This call is functionally equivalent to setting
  // secondary_url = wildcard.
  settings_map()->SetContentSettingDefaultScope(
      parent_url, other_url, ContentSettingsType::MIXEDSCRIPT,
      CONTENT_SETTING_ALLOW);

  blink::mojom::RendererContentSettingsPtr content_settings =
      NavigateAndGetContentSettings(parent_url, child_url);
  EXPECT_TRUE(content_settings->allow_mixed_content);
}
TEST_F(PageSpecificContentSettingsTest, Sensors) {
  NavigateAndCommit(GURL("http://google.com"));
  PageSpecificContentSettings* content_settings =
      PageSpecificContentSettings::GetForFrame(
          web_contents()->GetPrimaryMainFrame());

  EXPECT_FALSE(content_settings->is_any_requested_sensor_available());
  EXPECT_EQ(0, content_settings->active_available_sensors());

  content_settings->SetRequestedSensorIsAvailable(true);
  EXPECT_TRUE(content_settings->is_any_requested_sensor_available());

  content_settings->OnSensorStarted();
  EXPECT_EQ(1, content_settings->active_available_sensors());

  content_settings->OnSensorStarted();
  EXPECT_EQ(2, content_settings->active_available_sensors());

  content_settings->OnSensorStopped();
  EXPECT_EQ(1, content_settings->active_available_sensors());

  content_settings->OnSensorStopped();
  EXPECT_EQ(0, content_settings->active_available_sensors());
}

#endif  // !BUILDFLAG(IS_IOS)
}  // namespace content_settings
