chromium/ios/chrome/browser/web/model/chrome_web_client.mm

// 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 <string_view>

#import "base/apple/bundle_locations.h"
#import "base/command_line.h"
#import "base/feature_list.h"
#import "base/files/file_util.h"
#import "base/ios/ios_util.h"
#import "base/ios/ns_error_util.h"
#import "base/metrics/histogram_functions.h"
#import "base/no_destructor.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "components/autofill/ios/browser/suggestion_controller_java_script_feature.h"
#import "components/autofill/ios/form_util/form_handlers_java_script_feature.h"
#import "components/dom_distiller/core/url_constants.h"
#import "components/google/core/common/google_util.h"
#import "components/language/ios/browser/language_detection_java_script_feature.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/password_manager/ios/password_manager_java_script_feature.h"
#import "components/strings/grit/components_strings.h"
#import "components/supervised_user/core/browser/supervised_user_interstitial.h"
#import "components/translate/ios/browser/translate_java_script_feature.h"
#import "components/version_info/version_info.h"
#import "ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_java_script_feature.h"
#import "ios/chrome/browser/content_settings/model/host_content_settings_map_factory.h"
#import "ios/chrome/browser/flags/chrome_switches.h"
#import "ios/chrome/browser/follow/model/follow_java_script_feature.h"
#import "ios/chrome/browser/https_upgrades/model/https_upgrade_service_factory.h"
#import "ios/chrome/browser/link_to_text/model/link_to_text_java_script_feature.h"
#import "ios/chrome/browser/ntp/model/browser_policy_new_tab_page_rewriter.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/permissions/model/features.h"
#import "ios/chrome/browser/permissions/model/geolocation_api_usage_java_script_feature.h"
#import "ios/chrome/browser/permissions/model/media_api_usage_java_script_feature.h"
#import "ios/chrome/browser/prerender/model/prerender_service.h"
#import "ios/chrome/browser/prerender/model/prerender_service_factory.h"
#import "ios/chrome/browser/reading_list/model/offline_page_tab_helper.h"
#import "ios/chrome/browser/reading_list/model/offline_url_utils.h"
#import "ios/chrome/browser/safe_browsing/model/password_protection_java_script_feature.h"
#import "ios/chrome/browser/safe_browsing/model/safe_browsing_blocking_page.h"
#import "ios/chrome/browser/search_engines/model/search_engine_java_script_feature.h"
#import "ios/chrome/browser/search_engines/model/search_engine_tab_helper_factory.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/url/url_util.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/elements/windowed_container_view.h"
#import "ios/chrome/browser/ssl/model/ios_ssl_error_handler.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_error.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_error_container.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_interstitial_java_script_feature.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_url_filter_tab_helper.h"
#import "ios/chrome/browser/web/model/browser_about_rewriter.h"
#import "ios/chrome/browser/web/model/choose_file/choose_file_java_script_feature.h"
#import "ios/chrome/browser/web/model/chrome_main_parts.h"
#import "ios/chrome/browser/web/model/error_page_util.h"
#import "ios/chrome/browser/web/model/features.h"
#import "ios/chrome/browser/web/model/font_size/font_size_java_script_feature.h"
#import "ios/chrome/browser/web/model/image_fetch/image_fetch_java_script_feature.h"
#import "ios/chrome/browser/web/model/java_script_console/java_script_console_feature.h"
#import "ios/chrome/browser/web/model/java_script_console/java_script_console_feature_factory.h"
#import "ios/chrome/browser/web/model/print/print_java_script_feature.h"
#import "ios/chrome/browser/web/model/web_performance_metrics/web_performance_metrics_java_script_feature.h"
#import "ios/chrome/browser/web_selection/model/web_selection_java_script_feature.h"
#import "ios/chrome/common/channel_info.h"
#import "ios/components/security_interstitials/https_only_mode/feature.h"
#import "ios/components/security_interstitials/https_only_mode/https_only_mode_blocking_page.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_controller_client.h"
#import "ios/components/security_interstitials/https_only_mode/https_only_mode_error.h"
#import "ios/components/security_interstitials/https_only_mode/https_upgrade_service.h"
#import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
#import "ios/components/security_interstitials/ios_security_interstitial_java_script_feature.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_blocking_page.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
#import "ios/components/security_interstitials/lookalikes/lookalike_url_controller_client.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/components/webui/web_ui_url_constants.h"
#import "ios/net/protocol_handler_util.h"
#import "ios/public/provider/chrome/browser/url_rewriters/url_rewriters_api.h"
#import "ios/web/common/features.h"
#import "ios/web/common/user_agent.h"
#import "ios/web/public/find_in_page/crw_find_session.h"
#import "ios/web/public/navigation/browser_url_rewriter.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "net/base/net_errors.h"
#import "net/http/http_util.h"
#import "services/metrics/public/cpp/ukm_source_id.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/resource/resource_bundle.h"
#import "url/gurl.h"
namespace {
// The tag describing the product name with a placeholder for the version.
const char kProductTagWithPlaceholder[] = "CriOS/%s";

// Returns the safe browsing error page HTML.
NSString* GetSafeBrowsingErrorPageHTML(web::WebState* web_state,
                                       int64_t navigation_id) {
  // Fetch the unsafe resource causing this error page from the WebState's
  // container.
  SafeBrowsingUnsafeResourceContainer* container =
      SafeBrowsingUnsafeResourceContainer::FromWebState(web_state);
  const security_interstitials::UnsafeResource* resource =
      container->GetMainFrameUnsafeResource();

  // Construct the blocking page and associate it with the WebState.
  std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
      SafeBrowsingBlockingPage::Create(*resource);
  std::string error_page_content = page->GetHtmlContents();
  security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
      ->AssociateBlockingPage(navigation_id, std::move(page));

  return base::SysUTF8ToNSString(error_page_content);
}

// Returns the lookalike error page HTML.
NSString* GetLookalikeUrlErrorPageHtml(web::WebState* web_state,
                                       int64_t navigation_id) {
  // Fetch the lookalike URL info from the WebState's container.
  LookalikeUrlContainer* container =
      LookalikeUrlContainer::FromWebState(web_state);
  std::unique_ptr<LookalikeUrlContainer::LookalikeUrlInfo> lookalike_info =
      container->ReleaseLookalikeUrlInfo();

  // Construct the blocking page and associate it with the WebState.
  std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
      std::make_unique<LookalikeUrlBlockingPage>(
          web_state, lookalike_info->safe_url, lookalike_info->request_url,
          ukm::ConvertToSourceId(navigation_id,
                                 ukm::SourceIdType::NAVIGATION_ID),
          lookalike_info->match_type,
          std::make_unique<LookalikeUrlControllerClient>(
              web_state, lookalike_info->safe_url, lookalike_info->request_url,
              GetApplicationContext()->GetApplicationLocale()));
  std::string error_page_content = page->GetHtmlContents();
  security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
      ->AssociateBlockingPage(navigation_id, std::move(page));

  return base::SysUTF8ToNSString(error_page_content);
}

// Returns the HTTPS only mode error page HTML.
NSString* GetHttpsOnlyModeErrorPageHtml(web::WebState* web_state,
                                        int64_t navigation_id) {
  // Fetch the HTTP URL from the container.
  HttpsOnlyModeContainer* container =
      HttpsOnlyModeContainer::FromWebState(web_state);
  HttpsUpgradeService* service = HttpsUpgradeServiceFactory::GetForBrowserState(
      web_state->GetBrowserState());

  // Construct the blocking page and associate it with the WebState.
  std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
      std::make_unique<HttpsOnlyModeBlockingPage>(
          web_state, container->http_url(), service,
          std::make_unique<HttpsOnlyModeControllerClient>(
              web_state, container->http_url(),
              GetApplicationContext()->GetApplicationLocale()));

  std::string error_page_content = page->GetHtmlContents();
  security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
      ->AssociateBlockingPage(navigation_id, std::move(page));
  return base::SysUTF8ToNSString(error_page_content);
}

// Returns the Supervised User Error Page Interstitial HTML.
NSString* GetSupervisedUserErrorPageHTML(web::WebState* web_state,
                                         int64_t navigation_id,
                                         const GURL& url) {
  // Fetch the supervised user error info from the WebState's container.
  SupervisedUserErrorContainer* container =
      SupervisedUserErrorContainer::FromWebState(web_state);
  CHECK(container);
  std::unique_ptr<SupervisedUserErrorContainer::SupervisedUserErrorInfo>
      error_info = container->ReleaseSupervisedUserErrorInfo();
  CHECK(error_info);

  std::unique_ptr<supervised_user::SupervisedUserInterstitial> interstitial =
      container->CreateSupervisedUserInterstitial(*error_info);
  std::unique_ptr<security_interstitials::IOSSecurityInterstitialPage> page =
      std::make_unique<SupervisedUserInterstitialBlockingPage>(
          std::move(interstitial), /*controller_client=*/nullptr, container,
          web_state);

  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
  std::string error_page_content =
      supervised_user::SupervisedUserInterstitial::GetHTMLContents(
          SupervisedUserServiceFactory::GetForBrowserState(browser_state),
          browser_state->GetPrefs(), error_info->filtering_behavior_reason(),
          container->IsRemoteApprovalPendingForUrl(url),
          error_info->is_main_frame(),
          GetApplicationContext()->GetApplicationLocale());

  security_interstitials::IOSBlockingPageTabHelper::FromWebState(web_state)
      ->AssociateBlockingPage(navigation_id, std::move(page));
  return base::SysUTF8ToNSString(error_page_content);
}

// Returns a string describing the product name and version, of the
// form "productname/version". Used as part of the user agent string.
std::string GetMobileProduct() {
  return base::StringPrintf(kProductTagWithPlaceholder,
                            version_info::GetVersionNumber().data());
}

// Returns a string describing the product name and version, of the
// form "productname/version". Used as part of the user agent string.
// The Desktop UserAgent is only using the major version to reduce the surface
// for fingerprinting. The Mobile one is using the full version for legacy
// reasons.
std::string GetDesktopProduct() {
  return base::StringPrintf(kProductTagWithPlaceholder,
                            version_info::GetMajorVersionNumber().c_str());
}

// If `url` is an offline URL, returns the associated online URL. If it is not
// an offline URL then returns `url` as it can be considered as online.
GURL GetOnlineUrl(const GURL& url) {
  GURL online_url = url;
  if (reading_list::IsOfflineEntryURL(url)) {
    online_url = reading_list::EntryURLForOfflineURL(url);
  } else if (reading_list::IsOfflineReloadURL(url)) {
    online_url = reading_list::ReloadURLForOfflineURL(url);
  }
  return online_url;
}

}  // namespace

ChromeWebClient::ChromeWebClient() {}

ChromeWebClient::~ChromeWebClient() {}

std::unique_ptr<web::WebMainParts> ChromeWebClient::CreateWebMainParts() {
  return std::make_unique<IOSChromeMainParts>(
      *base::CommandLine::ForCurrentProcess());
}

void ChromeWebClient::PreWebViewCreation() const {}

void ChromeWebClient::AddAdditionalSchemes(Schemes* schemes) const {
  schemes->standard_schemes.push_back(kChromeUIScheme);
  schemes->secure_schemes.push_back(kChromeUIScheme);
}

std::string ChromeWebClient::GetApplicationLocale() const {
  DCHECK(GetApplicationContext());
  return GetApplicationContext()->GetApplicationLocale();
}

bool ChromeWebClient::IsAppSpecificURL(const GURL& url) const {
  return url.SchemeIs(kChromeUIScheme);
}

std::string ChromeWebClient::GetUserAgent(web::UserAgentType type) const {
  // The user agent should not be requested for app-specific URLs.
  DCHECK_NE(type, web::UserAgentType::NONE);

  // Using desktop user agent overrides a command-line user agent, so that
  // request desktop site can still work when using an overridden UA.
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (type != web::UserAgentType::DESKTOP &&
      command_line->HasSwitch(switches::kUserAgent)) {
    std::string user_agent =
        command_line->GetSwitchValueASCII(switches::kUserAgent);
    if (net::HttpUtil::IsValidHeaderValue(user_agent))
      return user_agent;
    LOG(WARNING) << "Ignored invalid value for flag --" << switches::kUserAgent;
  }

  if (type == web::UserAgentType::DESKTOP)
    return web::BuildDesktopUserAgent(GetDesktopProduct());
  return web::BuildMobileUserAgent(GetMobileProduct());
}

std::u16string ChromeWebClient::GetLocalizedString(int message_id) const {
  return l10n_util::GetStringUTF16(message_id);
}

std::string_view ChromeWebClient::GetDataResource(
    int resource_id,
    ui::ResourceScaleFactor scale_factor) const {
  return ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
      resource_id, scale_factor);
}

base::RefCountedMemory* ChromeWebClient::GetDataResourceBytes(
    int resource_id) const {
  return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
      resource_id);
}

void ChromeWebClient::GetAdditionalWebUISchemes(
    std::vector<std::string>* additional_schemes) {
  additional_schemes->push_back(dom_distiller::kDomDistillerScheme);
}

void ChromeWebClient::PostBrowserURLRewriterCreation(
    web::BrowserURLRewriter* rewriter) {
  rewriter->AddURLRewriter(&WillHandleWebBrowserNewTabPageURLForPolicy);
  rewriter->AddURLRewriter(&WillHandleWebBrowserAboutURL);
  ios::provider::AddURLRewriters(rewriter);
}

std::vector<web::JavaScriptFeature*> ChromeWebClient::GetJavaScriptFeatures(
    web::BrowserState* browser_state) const {
  static base::NoDestructor<PrintJavaScriptFeature> print_feature;
  std::vector<web::JavaScriptFeature*> features;
  if (base::FeatureList::IsEnabled(
          password_manager::features::kPasswordReuseDetectionEnabled)) {
    features.push_back(PasswordProtectionJavaScriptFeature::GetInstance());
  }

  JavaScriptConsoleFeature* java_script_console_feature =
      JavaScriptConsoleFeatureFactory::GetInstance()->GetForBrowserState(
          browser_state);
  features.push_back(java_script_console_feature);

  features.push_back(print_feature.get());

  features.push_back(autofill::AutofillJavaScriptFeature::GetInstance());
  features.push_back(autofill::FormHandlersJavaScriptFeature::GetInstance());
  features.push_back(
      autofill::SuggestionControllerJavaScriptFeature::GetInstance());
  features.push_back(AutofillBottomSheetJavaScriptFeature::GetInstance());
  features.push_back(FontSizeJavaScriptFeature::GetInstance());
  features.push_back(ImageFetchJavaScriptFeature::GetInstance());
  features.push_back(
      password_manager::PasswordManagerJavaScriptFeature::GetInstance());
  features.push_back(LinkToTextJavaScriptFeature::GetInstance());
  if (IsPartialTranslateEnabled() || IsSearchWithEnabled()) {
    features.push_back(WebSelectionJavaScriptFeature::GetInstance());
  }

  SearchEngineJavaScriptFeature::GetInstance()->SetDelegate(
      SearchEngineTabHelperFactory::GetInstance());
  features.push_back(SearchEngineJavaScriptFeature::GetInstance());
  features.push_back(
      security_interstitials::IOSSecurityInterstitialJavaScriptFeature::
          GetInstance());
  features.push_back(
      language::LanguageDetectionJavaScriptFeature::GetInstance());
  features.push_back(translate::TranslateJavaScriptFeature::GetInstance());
  features.push_back(WebPerformanceMetricsJavaScriptFeature::GetInstance());
  features.push_back(FollowJavaScriptFeature::GetInstance());
  features.push_back(ChooseFileJavaScriptFeature::GetInstance());

  features.push_back(
      SupervisedUserInterstitialJavaScriptFeature::GetInstance());

  if (base::FeatureList::IsEnabled(
          kJavaScriptPermissionBasedAPIMetricsEnabled)) {
    if (GeolocationAPIUsageJavaScriptFeature::ShouldOverrideAPI()) {
      features.push_back(GeolocationAPIUsageJavaScriptFeature::GetInstance());
    }
    if (MediaAPIUsageJavaScriptFeature::ShouldOverrideAPI()) {
      features.push_back(MediaAPIUsageJavaScriptFeature::GetInstance());
    }
  }

  return features;
}

void ChromeWebClient::PrepareErrorPage(
    web::WebState* web_state,
    const GURL& url,
    NSError* error,
    bool is_post,
    bool is_off_the_record,
    const std::optional<net::SSLInfo>& ssl_info,
    int64_t navigation_id,
    base::OnceCallback<void(NSString*)> callback) {
  OfflinePageTabHelper* offline_page_tab_helper =
      OfflinePageTabHelper::FromWebState(web_state);
  // WebState that are not attached to a tab may not have an
  // OfflinePageTabHelper.
  if (offline_page_tab_helper &&
      (offline_page_tab_helper->CanHandleErrorLoadingURL(url))) {
    // An offline version of the page will be displayed to replace this error
    // page. Loading an error page here can cause a race between the
    // navigation to load the error page and the navigation to display the
    // offline version of the page. If the latter navigation interrupts the
    // former and causes it to fail, this can incorrectly appear to be a
    // navigation back to the previous committed URL. To avoid this race,
    // return a nil error page here to avoid an error page load. See
    // crbug.com/980912.
    std::move(callback).Run(nil);
    return;
  }
  DCHECK(error);
  NSError* final_underlying_error =
      base::ios::GetFinalUnderlyingErrorFromError(error);
  if ([final_underlying_error.domain
          isEqualToString:kSafeBrowsingErrorDomain]) {
    // Only kUnsafeResourceErrorCode is supported.
    DCHECK_EQ(kUnsafeResourceErrorCode, final_underlying_error.code);
    std::move(callback).Run(
        GetSafeBrowsingErrorPageHTML(web_state, navigation_id));
  } else if ([final_underlying_error.domain
                 isEqualToString:kLookalikeUrlErrorDomain]) {
    // Only kLookalikeUrlErrorCode is supported.
    DCHECK_EQ(kLookalikeUrlErrorCode, final_underlying_error.code);
    std::move(callback).Run(
        GetLookalikeUrlErrorPageHtml(web_state, navigation_id));
  } else if ([final_underlying_error.domain
                 isEqualToString:kSupervisedUserInterstitialErrorDomain]) {
    CHECK_EQ(kSupervisedUserInterstitialErrorCode, final_underlying_error.code);
    std::move(callback).Run(
        GetSupervisedUserErrorPageHTML(web_state, navigation_id, url));
  } else if ([final_underlying_error.domain
                 isEqualToString:kHttpsOnlyModeErrorDomain]) {
    // Only kHttpsOnlyModeErrorCode is supported.
    DCHECK_EQ(kHttpsOnlyModeErrorCode, final_underlying_error.code);
    std::move(callback).Run(
        GetHttpsOnlyModeErrorPageHtml(web_state, navigation_id));
  } else if (ssl_info.has_value()) {
    IOSSSLErrorHandler::HandleSSLError(
        web_state, net::MapCertStatusToNetError(ssl_info.value().cert_status),
        ssl_info.value(), url, ssl_info.value().is_fatal_cert_error,
        navigation_id, std::move(callback));
  } else {
    std::move(callback).Run(
        GetErrorPage(url, error, is_post, is_off_the_record));
  }
}

UIView* ChromeWebClient::GetWindowedContainer() {
  if (!windowed_container_) {
    windowed_container_ = [[WindowedContainerView alloc] init];
  }
  return windowed_container_;
}

bool ChromeWebClient::EnableFullscreenAPI() const {
  // Only use the Fullscreen API on iOS 16.4+, which fixes serious crashes in
  // earlier versions. Also, only enable on iPad to match expectations of the
  // iOS web ecosystem.
  return base::ios::IsRunningOnOrLater(16, 4, 0) &&
         ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET;
}

bool ChromeWebClient::EnableLongPressUIContextMenu() const {
  return true;
}

bool ChromeWebClient::EnableWebInspector(
    web::BrowserState* browser_state) const {
  if (!web::features::IsWebInspectorSupportEnabled()) {
    return false;
  }

  ChromeBrowserState* chrome_browser_state =
      ChromeBrowserState::FromBrowserState(browser_state);
  return chrome_browser_state->GetPrefs()->GetBoolean(
      prefs::kWebInspectorEnabled);
}

web::UserAgentType ChromeWebClient::GetDefaultUserAgent(
    web::WebState* web_state,
    const GURL& url) const {
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
  HostContentSettingsMap* settings_map =
      ios::HostContentSettingsMapFactory::GetForBrowserState(browser_state);

  bool use_desktop_agent = ShouldLoadUrlInDesktopMode(url, settings_map);
  return use_desktop_agent ? web::UserAgentType::DESKTOP
                           : web::UserAgentType::MOBILE;
}

void ChromeWebClient::LogDefaultUserAgent(web::WebState* web_state,
                                          const GURL& url) const {
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
  HostContentSettingsMap* settings_map =
      ios::HostContentSettingsMapFactory::GetForBrowserState(browser_state);
  bool use_desktop_agent = ShouldLoadUrlInDesktopMode(url, settings_map);
  base::UmaHistogramBoolean("IOS.PageLoad.DefaultModeMobile",
                            !use_desktop_agent);
}

void ChromeWebClient::CleanupNativeRestoreURLs(web::WebState* web_state) const {
  web::NavigationManager* navigationManager = web_state->GetNavigationManager();
  for (int i = 0; i < web_state->GetNavigationItemCount(); i++) {
    // The WKWebView URL underneath the NTP is about://newtab/, which has no
    // title. When restoring the NTP, be sure to re-add the title below.
    web::NavigationItem* item = navigationManager->GetItemAtIndex(i);
    NewTabPageTabHelper::UpdateItem(item);

    // The WKWebView URL underneath a forced-offline page is chrome://offline,
    // which has an embedded entry URL. Apply that entryURL to the virtualURL
    // here.
    if (item->GetVirtualURL().host() == kChromeUIOfflineHost) {
      item->SetVirtualURL(
          reading_list::EntryURLForOfflineURL(item->GetVirtualURL()));
    }
  }
}

void ChromeWebClient::WillDisplayMediaCapturePermissionPrompt(
    web::WebState* web_state) const {
  // When a prendered page displays a prompt, cancel the prerender.
  PrerenderService* prerender_service =
      PrerenderServiceFactory::GetForBrowserState(
          ChromeBrowserState::FromBrowserState(web_state->GetBrowserState()));
  if (prerender_service &&
      prerender_service->IsWebStatePrerendered(web_state)) {
    prerender_service->CancelPrerender();
  }
}

bool ChromeWebClient::IsPointingToSameDocument(const GURL& url1,
                                               const GURL& url2) const {
  GURL url_to_compare1 = GetOnlineUrl(url1);
  GURL url_to_compare2 = GetOnlineUrl(url2);
  return url_to_compare1 == url_to_compare2;
}

bool ChromeWebClient::IsBrowserLockdownModeEnabled(
    web::BrowserState* browser_state) {
  ChromeBrowserState* chrome_browser_state =
      ChromeBrowserState::FromBrowserState(browser_state);
  PrefService* prefs = chrome_browser_state->GetPrefs();
  return prefs->GetBoolean(prefs::kBrowserLockdownModeEnabled);
}

void ChromeWebClient::SetOSLockdownModeEnabled(web::BrowserState* browser_state,
                                               bool enabled) {
  ChromeBrowserState* chrome_browser_state =
      ChromeBrowserState::FromBrowserState(browser_state);
  PrefService* prefs = chrome_browser_state->GetPrefs();
  prefs->SetBoolean(prefs::kOSLockdownModeEnabled, enabled);
}

bool ChromeWebClient::IsInsecureFormWarningEnabled(
    web::BrowserState* browser_state) const {
  ChromeBrowserState* chrome_browser_state =
      ChromeBrowserState::FromBrowserState(browser_state);
  if (!chrome_browser_state->GetPrefs()->GetBoolean(
          prefs::kInsecureFormWarningsEnabled) &&
      chrome_browser_state->GetPrefs()->IsManagedPreference(
          prefs::kInsecureFormWarningsEnabled)) {
    return false;
  }
  return base::FeatureList::IsEnabled(
      security_interstitials::features::kInsecureFormSubmissionInterstitial);
}