// 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/web_view/internal/web_view_web_client.h"

#import <dispatch/dispatch.h>

#import <string_view>

#import "base/apple/bundle_locations.h"
#import "base/check.h"
#import "base/functional/bind.h"
#import "base/functional/callback_helpers.h"
#import "base/ios/ns_error_util.h"
#import "base/memory/ref_counted_memory.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/autofill/ios/form_util/programmatic_form_submission_handler_java_script_feature.h"
#import "components/language/ios/browser/language_detection_java_script_feature.h"
#import "components/password_manager/ios/password_manager_java_script_feature.h"
#import "components/security_interstitials/core/unsafe_resource.h"
#import "components/ssl_errors/error_info.h"
#import "components/strings/grit/components_strings.h"
#import "components/translate/ios/browser/translate_java_script_feature.h"
#import "ios/components/security_interstitials/https_only_mode/feature.h"
#import "ios/components/security_interstitials/ios_security_interstitial_java_script_feature.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/components/webui/web_ui_url_constants.h"
#import "ios/web/common/user_agent.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/security/ssl_status.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "ios/web/public/thread/web_thread.h"
#import "ios/web_view/internal/cwv_global_state_internal.h"
#import "ios/web_view/internal/cwv_lookalike_url_handler_internal.h"
#import "ios/web_view/internal/cwv_ssl_error_handler_internal.h"
#import "ios/web_view/internal/cwv_ssl_status_internal.h"
#import "ios/web_view/internal/cwv_ssl_util.h"
#import "ios/web_view/internal/cwv_web_view_internal.h"
#import "ios/web_view/internal/js_messaging/web_view_scripts_java_script_feature.h"
#import "ios/web_view/internal/safe_browsing/cwv_unsafe_url_handler_internal.h"
#import "ios/web_view/internal/web_view_browser_state.h"
#import "ios/web_view/internal/web_view_message_handler_java_script_feature.h"
#import "ios/web_view/internal/web_view_web_main_parts.h"
#import "ios/web_view/public/cwv_navigation_delegate.h"
#import "ios/web_view/public/cwv_ui_delegate.h"
#import "ios/web_view/public/cwv_web_view.h"
#import "net/base/apple/url_conversions.h"
#import "net/cert/cert_status_flags.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/resource/resource_bundle.h"

namespace ios_web_view {

WebViewWebClient::WebViewWebClient() = default;

WebViewWebClient::~WebViewWebClient() = default;

std::unique_ptr<web::WebMainParts> WebViewWebClient::CreateWebMainParts() {
  return std::make_unique<WebViewWebMainParts>();
}

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

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

std::string WebViewWebClient::GetUserAgent(web::UserAgentType type) const {
  if (CWVGlobalState.sharedInstance.customUserAgent) {
    return base::SysNSStringToUTF8(
        CWVGlobalState.sharedInstance.customUserAgent);
  } else {
    return web::BuildMobileUserAgent(base::SysNSStringToUTF8(
        CWVGlobalState.sharedInstance.userAgentProduct));
  }
}

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

scoped_refptr<base::RefCountedMemory> WebViewWebClient::GetDataResourceBytes(
    int resource_id) const {
  return ui::ResourceBundle::GetSharedInstance().LoadDataResourceBytes(
      resource_id);
}

std::vector<web::JavaScriptFeature*> WebViewWebClient::GetJavaScriptFeatures(
    web::BrowserState* browser_state) const {
  std::vector<web::JavaScriptFeature*> features = {
      autofill::AutofillJavaScriptFeature::GetInstance(),
      autofill::FormHandlersJavaScriptFeature::GetInstance(),
      autofill::SuggestionControllerJavaScriptFeature::GetInstance(),
      language::LanguageDetectionJavaScriptFeature::GetInstance(),
      password_manager::PasswordManagerJavaScriptFeature::GetInstance(),
      security_interstitials::IOSSecurityInterstitialJavaScriptFeature::
          GetInstance(),
      translate::TranslateJavaScriptFeature::GetInstance(),
      WebViewMessageHandlerJavaScriptFeature::FromBrowserState(browser_state),
      WebViewScriptsJavaScriptFeature::FromBrowserState(browser_state),
      autofill::ProgrammaticFormSubmissionHandlerJavaScriptFeature::
          GetInstance()};

  return features;
}

void WebViewWebClient::PrepareErrorPage(
    web::WebState* web_state,
    const GURL& url,
    NSError* error,
    bool is_post,
    bool is_off_the_record,
    const std::optional<net::SSLInfo>& info,
    int64_t navigation_id,
    base::OnceCallback<void(NSString*)> callback) {
  DCHECK(error);

  CWVWebView* web_view = [CWVWebView webViewForWebState:web_state];
  id<CWVNavigationDelegate> navigation_delegate = web_view.navigationDelegate;

  // |final_underlying_error| should be checked first for any specific error
  // cases such as lookalikes and safebrowsing errors. |info| is only non-empty
  // if this is a SSL related error.
  NSError* final_underlying_error =
      base::ios::GetFinalUnderlyingErrorFromError(error);
  if ([final_underlying_error.domain isEqual:kSafeBrowsingErrorDomain] &&
      [navigation_delegate
          respondsToSelector:@selector(webView:handleUnsafeURLWithHandler:)]) {
    DCHECK_EQ(SafeBrowsingErrorCode::kUnsafeResource,
              static_cast<SafeBrowsingErrorCode>(final_underlying_error.code));
    SafeBrowsingUnsafeResourceContainer* container =
        SafeBrowsingUnsafeResourceContainer::FromWebState(web_state);
    const security_interstitials::UnsafeResource* resource =
        container->GetMainFrameUnsafeResource();
    CWVUnsafeURLHandler* handler =
        [[CWVUnsafeURLHandler alloc] initWithWebState:web_state
                                       unsafeResource:*resource
                                         htmlCallback:std::move(callback)];
    [navigation_delegate webView:web_view handleUnsafeURLWithHandler:handler];
  } else if ([final_underlying_error.domain isEqual:kLookalikeUrlErrorDomain] &&
             [navigation_delegate respondsToSelector:@selector
                                  (webView:handleLookalikeURLWithHandler:)]) {
    DCHECK_EQ(kLookalikeUrlErrorCode, final_underlying_error.code);
    LookalikeUrlContainer* container =
        LookalikeUrlContainer::FromWebState(web_state);
    std::unique_ptr<LookalikeUrlContainer::LookalikeUrlInfo> lookalike_info =
        container->ReleaseLookalikeUrlInfo();
    CWVLookalikeURLHandler* handler = [[CWVLookalikeURLHandler alloc]
        initWithWebState:web_state
        lookalikeURLInfo:std::move(lookalike_info)
            htmlCallback:std::move(callback)];
    [navigation_delegate webView:web_view
        handleLookalikeURLWithHandler:handler];
  } else if (info.has_value() &&
             [navigation_delegate respondsToSelector:@selector
                                  (webView:handleSSLErrorWithHandler:)]) {
    CWVSSLErrorHandler* handler = [[CWVSSLErrorHandler alloc]
             initWithWebState:web_state
                          URL:net::NSURLWithGURL(url)
                        error:error
                      SSLInfo:info.value()
        errorPageHTMLCallback:base::CallbackToBlock(std::move(callback))];
    [navigation_delegate webView:web_view handleSSLErrorWithHandler:handler];
  } else {
    std::move(callback).Run(error.localizedDescription);
  }
}

bool WebViewWebClient::EnableLongPressUIContextMenu() const {
  return CWVWebView.chromeContextMenuEnabled;
}

bool WebViewWebClient::EnableWebInspector(
    web::BrowserState* browser_state) const {
  return CWVWebView.webInspectorEnabled;
}

bool WebViewWebClient::IsInsecureFormWarningEnabled(
    web::BrowserState* browser_state) const {
  // ios/web_view doesn't receive variations seeds at runtime, so this will
  // only ever use the default value of the feature.
  return base::FeatureList::IsEnabled(
      security_interstitials::features::kInsecureFormSubmissionInterstitial);
}

void WebViewWebClient::BuildEditMenu(web::WebState* web_state,
                                     id<UIMenuBuilder> builder) const {
  CWVWebView* web_view = [CWVWebView webViewForWebState:web_state];
  id<CWVUIDelegate> ui_delegate = web_view.UIDelegate;
  if ([ui_delegate
          respondsToSelector:@selector(webView:buildMenuWithBuilder:)]) {
    [ui_delegate webView:web_view buildMenuWithBuilder:builder];
  }
}

}  // namespace ios_web_view
