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

#import "ios/chrome/browser/web/model/chrome_web_client.h"

#import <UIKit/UIKit.h>

#import <algorithm>
#import <memory>

#import "base/feature_list.h"
#import "base/numerics/safe_conversions.h"
#import "base/run_loop.h"
#import "base/strings/string_split.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/test/scoped_feature_list.h"
#import "components/captive_portal/core/captive_portal_detector.h"
#import "components/content_settings/core/browser/host_content_settings_map.h"
#import "components/lookalikes/core/lookalike_url_util.h"
#import "components/reading_list/core/reading_list_entry.h"
#import "components/reading_list/core/reading_list_model.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
#import "components/security_interstitials/core/unsafe_resource.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/content_settings/model/host_content_settings_map_factory.h"
#import "ios/chrome/browser/reading_list/model/reading_list_model_factory.h"
#import "ios/chrome/browser/reading_list/model/reading_list_test_utils.h"
#import "ios/chrome/browser/safe_browsing/model/client_side_detection/client_side_detection_java_script_feature.h"
#import "ios/chrome/browser/safe_browsing/model/safe_browsing_blocking_page.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/ssl/model/captive_portal_tab_helper.h"
#import "ios/chrome/browser/web/model/error_page_util.h"
#import "ios/components/security_interstitials/https_only_mode/https_only_mode_container.h"
#import "ios/components/security_interstitials/https_only_mode/https_only_mode_error.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_error.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_error.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_unsafe_resource_container.h"
#import "ios/net/protocol_handler_util.h"
#import "ios/web/common/features.h"
#import "ios/web/common/web_view_creation_util.h"
#import "ios/web/public/test/error_test_util.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/js_test_util.h"
#import "ios/web/public/test/scoped_testing_web_client.h"
#import "ios/web/public/test/web_task_environment.h"
#import "net/base/net_errors.h"
#import "net/http/http_status_code.h"
#import "net/ssl/ssl_info.h"
#import "net/test/cert_test_util.h"
#import "net/test/test_data_directory.h"
#import "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#import "services/network/public/mojom/fetch_api.mojom.h"
#import "services/network/test/test_url_loader_factory.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#import "ui/base/l10n/l10n_util.h"

using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;

namespace {
const char kTestUrl[] = "http://chromium.test";

// Error used to test PrepareErrorPage method.
NSError* CreateTestError() {
  return web::testing::CreateTestNetError([NSError
      errorWithDomain:NSURLErrorDomain
                 code:NSURLErrorNetworkConnectionLost
             userInfo:@{
               NSURLErrorFailingURLErrorKey :
                   [NSURL URLWithString:base::SysUTF8ToNSString(kTestUrl)]
             }]);
}
}  // namespace

class ChromeWebClientTest : public PlatformTest {
 public:
  ChromeWebClientTest() { profile_ = TestProfileIOS::Builder().Build(); }

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

  ~ChromeWebClientTest() override = default;

  ProfileIOS* profile() { return profile_.get(); }

 protected:
  web::WebTaskEnvironment environment_{
      web::WebTaskEnvironment::MainThreadType::IO};
  std::unique_ptr<TestProfileIOS> profile_;
};

TEST_F(ChromeWebClientTest, UserAgent) {
  std::vector<std::string> pieces;

  // Check if the pieces of the user agent string come in the correct order.
  ChromeWebClient web_client;
  std::string buffer = web_client.GetUserAgent(web::UserAgentType::MOBILE);

  pieces = base::SplitStringUsingSubstr(
      buffer, "Mozilla/5.0 (", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  ASSERT_EQ(2u, pieces.size());
  buffer = pieces[1];
  EXPECT_EQ("", pieces[0]);

  pieces = base::SplitStringUsingSubstr(
      buffer, ") AppleWebKit/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  ASSERT_EQ(2u, pieces.size());
  buffer = pieces[1];
  std::string os_str = pieces[0];

  pieces =
      base::SplitStringUsingSubstr(buffer, " (KHTML, like Gecko) ",
                                   base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  ASSERT_EQ(2u, pieces.size());
  buffer = pieces[1];
  std::string webkit_version_str = pieces[0];

  pieces = base::SplitStringUsingSubstr(
      buffer, " Safari/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  ASSERT_EQ(2u, pieces.size());
  std::string product_str = pieces[0];
  std::string safari_version_str = pieces[1];

  // Not sure what can be done to better check the OS string, since it's highly
  // platform-dependent.
  EXPECT_FALSE(os_str.empty());

  EXPECT_FALSE(webkit_version_str.empty());
  EXPECT_FALSE(safari_version_str.empty());

  EXPECT_EQ(0u, product_str.find("CriOS/"));
}

// Tests PrepareErrorPage wth non-post, not Off The Record error.
TEST_F(ChromeWebClientTest, PrepareErrorPageNonPostNonOtr) {
  ChromeWebClient web_client;
  NSError* error = CreateTestError();
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });
  web::FakeWebState web_state;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::nullopt,
                              /*navigation_id=*/0, std::move(callback));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    base::RunLoop().RunUntilIdle();
    return callback_called;
  }));
  EXPECT_NSEQ(GetErrorPage(GURL(kTestUrl), error, /*is_post=*/false,
                           /*is_off_the_record=*/false),
              page);
}

// Tests PrepareErrorPage with post, not Off The Record error.
TEST_F(ChromeWebClientTest, PrepareErrorPagePostNonOtr) {
  ChromeWebClient web_client;
  NSError* error = CreateTestError();
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });
  web::FakeWebState web_state;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/true,
                              /*is_off_the_record=*/false,
                              /*info=*/std::nullopt,
                              /*navigation_id=*/0, std::move(callback));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    base::RunLoop().RunUntilIdle();
    return callback_called;
  }));
  EXPECT_NSEQ(GetErrorPage(GURL(kTestUrl), error, /*is_post=*/true,
                           /*is_off_the_record=*/false),
              page);
}

// Tests PrepareErrorPage with non-post, Off The Record error.
TEST_F(ChromeWebClientTest, PrepareErrorPageNonPostOtr) {
  ChromeWebClient web_client;
  NSError* error = CreateTestError();
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });
  web::FakeWebState web_state;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/true,
                              /*info=*/std::nullopt,
                              /*navigation_id=*/0, std::move(callback));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    base::RunLoop().RunUntilIdle();
    return callback_called;
  }));
  EXPECT_NSEQ(GetErrorPage(GURL(kTestUrl), error, /*is_post=*/false,
                           /*is_off_the_record=*/true),
              page);
}

// Tests PrepareErrorPage with post, Off The Record error.
TEST_F(ChromeWebClientTest, PrepareErrorPagePostOtr) {
  ChromeWebClient web_client;
  NSError* error = CreateTestError();
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });
  web::FakeWebState web_state;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/true,
                              /*is_off_the_record=*/true,
                              /*info=*/std::nullopt,
                              /*navigation_id=*/0, std::move(callback));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    base::RunLoop().RunUntilIdle();
    return callback_called;
  }));
  EXPECT_NSEQ(GetErrorPage(GURL(kTestUrl), error, /*is_post=*/true,
                           /*is_off_the_record=*/true),
              page);
}

// Tests PrepareErrorPage with SSLInfo, which results in an SSL committed
// interstitial.
TEST_F(ChromeWebClientTest, PrepareErrorPageWithSSLInfo) {
  net::SSLInfo info;
  info.cert =
      net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
  info.is_fatal_cert_error = false;
  info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
  std::optional<net::SSLInfo> ssl_info = info;
  ChromeWebClient web_client;
  NSError* error =
      [NSError errorWithDomain:NSURLErrorDomain
                          code:NSURLErrorServerCertificateHasUnknownRoot
                      userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });
  web::FakeWebState web_state;
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);

  // Use a test URLLoaderFactory so that the captive portal detector doesn't
  // make an actual network request.
  network::TestURLLoaderFactory test_loader_factory;
  test_loader_factory.AddResponse(
      captive_portal::CaptivePortalDetector::GetDefaultUrl(), "",
      net::HTTP_NO_CONTENT);
  profile_->SetSharedURLLoaderFactory(
      base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
          &test_loader_factory));

  CaptivePortalTabHelper::CreateForWebState(&web_state);
  web_state.SetBrowserState(profile());
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/ssl_info,
                              /*navigation_id=*/0, std::move(callback));
  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^bool {
    base::RunLoop().RunUntilIdle();
    return callback_called;
  }));
  NSString* error_string = base::SysUTF8ToNSString(
      net::ErrorToShortString(net::ERR_CERT_COMMON_NAME_INVALID));
  EXPECT_TRUE([page containsString:error_string]);
}

class ChromeWebClientTest_V4V5 : public ChromeWebClientTest,
                                 public ::testing::WithParamInterface<bool> {
 public:
  ChromeWebClientTest_V4V5() {
    feature_list_.InitWithFeatureState(safe_browsing::kLocalListsUseSBv5,
                                       GetParam());
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         ChromeWebClientTest_V4V5,
                         ::testing::Bool(),
                         testing::PrintToStringParamName());

// Tests PrepareErrorPage for a safe browsing error, which results in a
// committed safe browsing interstitial.
TEST_P(ChromeWebClientTest_V4V5, PrepareErrorPageForSafeBrowsingError) {
  // Store an unsafe resource in `web_state`'s container.
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());
  SafeBrowsingUrlAllowList::CreateForWebState(&web_state);
  SafeBrowsingUnsafeResourceContainer::CreateForWebState(&web_state);
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);

  security_interstitials::UnsafeResource resource;
  resource.threat_type =
      safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING;
  resource.url = GURL("http://www.chromium.test");
  resource.weak_web_state = web_state.GetWeakPtr();
  // Added to ensure that `threat_source` isn't considered UNKNOWN in this case.
  resource.threat_source =
      GetParam() ? safe_browsing::ThreatSource::LOCAL_PVER5_LOCAL_BLOCKLIST
                 : safe_browsing::ThreatSource::LOCAL_PVER4;
  SafeBrowsingUrlAllowList::FromWebState(&web_state)
      ->AddPendingUnsafeNavigationDecision(resource.url, resource.threat_type);
  SafeBrowsingUnsafeResourceContainer::FromWebState(&web_state)
      ->StoreMainFrameUnsafeResource(resource);

  NSError* error =
      [NSError errorWithDomain:kSafeBrowsingErrorDomain
                          code:base::checked_cast<NSInteger>(
                                   SafeBrowsingErrorCode::kUnsafeResource)
                      userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });

  ChromeWebClient web_client;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::optional<net::SSLInfo>(),
                              /*navigation_id=*/0, std::move(callback));

  EXPECT_TRUE(callback_called);
  NSString* error_string = l10n_util::GetNSString(IDS_SAFEBROWSING_HEADING);
  EXPECT_TRUE([page containsString:error_string]);
}

// Tests PrepareErrorPage with a Reading List entry, which would normally
// trigger the offline page bypass, but with a Safe Browsing error (which is a
// security error), so the Safe Browsing interstitial should NOT be bypassed and
// must be displayed instead.


// Tests PrepareErrorPage for a safe browsing enterprise block error, which
// results in a committed enterprise interstitial.
TEST_F(ChromeWebClientTest,
       PrepareErrorPageForSafeBrowsingEnterpriseBlockError) {
  // Store an unsafe resource in `web_state`'s container.
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());
  SafeBrowsingUrlAllowList::CreateForWebState(&web_state);
  SafeBrowsingUnsafeResourceContainer::CreateForWebState(&web_state);
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);

  security_interstitials::UnsafeResource resource;
  resource.threat_type =
      safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_BLOCK;
  resource.url = GURL("http://www.chromium.test");
  resource.weak_web_state = web_state.GetWeakPtr();
  // Added to ensure that `threat_source` isn't considered UNKNOWN in this case.
  resource.threat_source = safe_browsing::ThreatSource::URL_REAL_TIME_CHECK;
  SafeBrowsingUrlAllowList::FromWebState(&web_state)
      ->AddPendingUnsafeNavigationDecision(resource.url, resource.threat_type);
  SafeBrowsingUnsafeResourceContainer::FromWebState(&web_state)
      ->StoreMainFrameUnsafeResource(resource);

  NSError* error = [NSError
      errorWithDomain:kSafeBrowsingErrorDomain
                 code:(NSInteger)SafeBrowsingErrorCode::kEnterpriseBlock
             userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });

  ChromeWebClient web_client;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::optional<net::SSLInfo>(),
                              /*navigation_id=*/0, std::move(callback));

  EXPECT_TRUE(callback_called);
  NSString* error_string = l10n_util::GetNSString(IDS_ENTERPRISE_BLOCK_HEADING);
  EXPECT_TRUE([page containsString:error_string]);
}

// Tests PrepareErrorPage for a safe browsing enterprise warn error, which
// results in a committed enterprise interstitial.
TEST_F(ChromeWebClientTest,
       PrepareErrorPageForSafeBrowsingEnterpriseWarnError) {
  // Store an unsafe resource in `web_state`'s container.
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());
  SafeBrowsingUrlAllowList::CreateForWebState(&web_state);
  SafeBrowsingUnsafeResourceContainer::CreateForWebState(&web_state);
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);

  security_interstitials::UnsafeResource resource;
  resource.threat_type =
      safe_browsing::SBThreatType::SB_THREAT_TYPE_MANAGED_POLICY_WARN;
  resource.url = GURL("http://www.chromium.test");
  resource.weak_web_state = web_state.GetWeakPtr();
  // Added to ensure that `threat_source` isn't considered UNKNOWN in this case.
  resource.threat_source = safe_browsing::ThreatSource::URL_REAL_TIME_CHECK;
  SafeBrowsingUrlAllowList::FromWebState(&web_state)
      ->AddPendingUnsafeNavigationDecision(resource.url, resource.threat_type);
  SafeBrowsingUnsafeResourceContainer::FromWebState(&web_state)
      ->StoreMainFrameUnsafeResource(resource);

  NSError* error =
      [NSError errorWithDomain:kSafeBrowsingErrorDomain
                          code:(NSInteger)SafeBrowsingErrorCode::kEnterpriseWarn
                      userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });

  ChromeWebClient web_client;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::optional<net::SSLInfo>(),
                              /*navigation_id=*/0, std::move(callback));

  EXPECT_TRUE(callback_called);
  NSString* error_string = l10n_util::GetNSString(IDS_ENTERPRISE_WARN_HEADING);
  EXPECT_TRUE([page containsString:error_string]);
}

// Tests PrepareErrorPage for a lookalike error, which results in a
// committed lookalike interstitial.
TEST_F(ChromeWebClientTest, PrepareErrorPageForLookalikeUrlError) {
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());
  LookalikeUrlContainer::CreateForWebState(&web_state);
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);
  auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
  web_state.SetNavigationManager(std::move(navigation_manager));

  LookalikeUrlContainer::FromWebState(&web_state)
      ->SetLookalikeUrlInfo(
          GURL("https://www.safe.test"), GURL(kTestUrl),
          lookalikes::LookalikeUrlMatchType::kSkeletonMatchTop5k);

  NSError* error = [NSError errorWithDomain:kLookalikeUrlErrorDomain
                                       code:kLookalikeUrlErrorCode
                                   userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });

  ChromeWebClient web_client;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::optional<net::SSLInfo>(),
                              /*navigation_id=*/0, std::move(callback));

  EXPECT_TRUE(callback_called);
  NSString* error_string =
      l10n_util::GetNSString(IDS_LOOKALIKE_URL_PRIMARY_PARAGRAPH);
  EXPECT_TRUE([page containsString:error_string])
      << base::SysNSStringToUTF8(page);
}

// Tests PrepareErrorPage for a lookalike error with no suggested URL,
// which results in a committed lookalike interstitial that has a 'Close page'
// button instead of 'Back to safety' (when there is no back item).
TEST_F(ChromeWebClientTest, PrepareErrorPageForLookalikeUrlErrorNoSuggestion) {
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());
  LookalikeUrlContainer::CreateForWebState(&web_state);
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);
  auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
  web_state.SetNavigationManager(std::move(navigation_manager));

  LookalikeUrlContainer::FromWebState(&web_state)
      ->SetLookalikeUrlInfo(
          GURL(""), GURL(kTestUrl),
          lookalikes::LookalikeUrlMatchType::kSkeletonMatchTop5k);

  NSError* error = [NSError errorWithDomain:kLookalikeUrlErrorDomain
                                       code:kLookalikeUrlErrorCode
                                   userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });

  ChromeWebClient web_client;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::optional<net::SSLInfo>(),
                              /*navigation_id=*/0, std::move(callback));

  EXPECT_TRUE(callback_called);
  NSString* close_page_string =
      l10n_util::GetNSString(IDS_LOOKALIKE_URL_CLOSE_PAGE);
  NSString* back_to_safety_string =
      l10n_util::GetNSString(IDS_LOOKALIKE_URL_BACK_TO_SAFETY);
  EXPECT_TRUE([page containsString:close_page_string])
      << base::SysNSStringToUTF8(page);
  EXPECT_FALSE([page containsString:back_to_safety_string])
      << base::SysNSStringToUTF8(page);
}

// Tests PrepareErrorPage for a HTTPS-Only Mode error, which results in a
// committed HTTPS-Only Mode interstitial that has a 'Go back'.
TEST_F(ChromeWebClientTest, PrepareErrorPageForHttpsOnlyModeError) {
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());
  HttpsOnlyModeContainer::CreateForWebState(&web_state);
  security_interstitials::IOSBlockingPageTabHelper::CreateForWebState(
      &web_state);
  auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
  web_state.SetNavigationManager(std::move(navigation_manager));

  HttpsOnlyModeContainer::FromWebState(&web_state)->SetHttpUrl(GURL(kTestUrl));

  NSError* error = [NSError errorWithDomain:kHttpsOnlyModeErrorDomain
                                       code:kHttpsOnlyModeErrorCode
                                   userInfo:nil];
  __block bool callback_called = false;
  __block NSString* page = nil;
  base::OnceCallback<void(NSString*)> callback =
      base::BindOnce(^(NSString* error_html) {
        callback_called = true;
        page = error_html;
      });

  ChromeWebClient web_client;
  web_client.PrepareErrorPage(&web_state, GURL(kTestUrl), error,
                              /*is_post=*/false,
                              /*is_off_the_record=*/false,
                              /*info=*/std::optional<net::SSLInfo>(),
                              /*navigation_id=*/0, std::move(callback));

  EXPECT_TRUE(callback_called);
  NSString* back_to_safety_string =
      l10n_util::GetNSString(IDS_HTTPS_ONLY_MODE_BACK_BUTTON);
  EXPECT_TRUE([page containsString:back_to_safety_string])
      << base::SysNSStringToUTF8(page);
}

// Tests the default user agent for different views.
TEST_F(ChromeWebClientTest, DefaultUserAgent) {
  ChromeWebClient web_client;
  web::FakeWebState web_state;
  web_state.SetBrowserState(profile());

  scoped_refptr<HostContentSettingsMap> settings_map(
      ios::HostContentSettingsMapFactory::GetForProfile(profile()));
  settings_map->SetContentSettingCustomScope(
      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
      ContentSettingsType::REQUEST_DESKTOP_SITE, CONTENT_SETTING_BLOCK);

  EXPECT_EQ(web::UserAgentType::MOBILE,
            web_client.GetDefaultUserAgent(&web_state, GURL()));

  settings_map->SetContentSettingCustomScope(
      ContentSettingsPattern::Wildcard(), ContentSettingsPattern::Wildcard(),
      ContentSettingsType::REQUEST_DESKTOP_SITE, CONTENT_SETTING_ALLOW);

  EXPECT_EQ(web::UserAgentType::DESKTOP,
            web_client.GetDefaultUserAgent(&web_state, GURL()));
}

// Tests if two online URLs are correctly processed.
TEST_F(ChromeWebClientTest, IsPointingToSameDocumentOnline) {
  ChromeWebClient web_client;
  GURL same_url1 = GURL("http://chromium.org/foo");
  GURL same_url2 = GURL("http://chromium.org/foo");

  EXPECT_TRUE(web_client.IsPointingToSameDocument(same_url1, same_url2));

  GURL different_url1 = GURL("http://chromium.org/foo");
  GURL different_url2 = GURL("http://chromium.org/bar");

  EXPECT_FALSE(
      web_client.IsPointingToSameDocument(different_url1, different_url2));
}



// Tests if URLs with one empty is working as expected.
TEST_F(ChromeWebClientTest, IsPointingToSameDocumentEmpty) {
  ChromeWebClient web_client;
  GURL online_url = GURL("http://chromium.org/foo");

  EXPECT_FALSE(web_client.IsPointingToSameDocument(GURL(), online_url));
}

// Tests that GetJavaScriptFeatures includes or excludes
// ClientSideDetectionJavaScriptFeature based on the feature flag.
TEST_F(ChromeWebClientTest, GetJavaScriptFeatures_ClientSideDetection) {
  ChromeWebClient web_client;
  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndEnableFeature(
        safe_browsing::kClientSideDetectionEnabledIos);
    auto features = web_client.GetJavaScriptFeatures(profile());
    EXPECT_TRUE(std::ranges::contains(
        features, ClientSideDetectionJavaScriptFeature::GetInstance()));
  }
  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndDisableFeature(
        safe_browsing::kClientSideDetectionEnabledIos);
    auto features = web_client.GetJavaScriptFeatures(profile());
    EXPECT_FALSE(std::ranges::contains(
        features, ClientSideDetectionJavaScriptFeature::GetInstance()));
  }
}
