text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "android_webview/browser/aw_content_browser_client.h"
  
  #include <utility>
  
  #include "android_webview/browser/aw_browser_context.h"
  #include "android_webview/browser/aw_browser_main_parts.h"
  #include "android_webview/browser/aw_contents.h"
  #include "android_webview/browser/aw_contents_client_bridge.h"
  #include "android_webview/browser/aw_contents_io_thread_client.h"
  #include "android_webview/browser/aw_cookie_access_policy.h"
  #include "android_webview/browser/aw_devtools_manager_delegate.h"
  #include "android_webview/browser/aw_printing_message_filter.h"
  #include "android_webview/browser/aw_quota_permission_context.h"
  #include "android_webview/browser/aw_settings.h"
  #include "android_webview/browser/aw_web_contents_view_delegate.h"
  #include "android_webview/browser/net/aw_url_request_context_getter.h"
  #include "android_webview/browser/renderer_host/aw_resource_dispatcher_host_delegate.h"
  #include "android_webview/browser/tracing/aw_tracing_delegate.h"
  #include "android_webview/common/aw_descriptors.h"
  #include "android_webview/common/aw_switches.h"
  #include "android_webview/common/crash_reporter/aw_microdump_crash_reporter.h"
  #include "android_webview/common/render_view_messages.h"
  #include "android_webview/common/url_constants.h"
  #include "android_webview/grit/aw_resources.h"
  #include "base/android/locale_utils.h"
  #include "base/base_paths_android.h"
  #include "base/base_switches.h"
  #include "base/command_line.h"
  #include "base/files/scoped_file.h"
  #include "base/json/json_reader.h"
  #include "base/memory/ptr_util.h"
  #include "base/path_service.h"
  #include "components/autofill/content/browser/content_autofill_driver_factory.h"
  #include "components/cdm/browser/cdm_message_filter_android.h"
  #include "components/crash/content/browser/crash_dump_observer_android.h"
  #include "components/navigation_interception/intercept_navigation_delegate.h"
  #include "components/spellcheck/spellcheck_build_features.h"
  #include "content/public/browser/browser_message_filter.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/child_process_security_policy.h"
  #include "content/public/browser/client_certificate_delegate.h"
  #include "content/public/browser/navigation_handle.h"
  #include "content/public/browser/navigation_throttle.h"
  #include "content/public/browser/render_frame_host.h"
  #include "content/public/browser/render_process_host.h"
  #include "content/public/browser/render_view_host.h"
  #include "content/public/browser/storage_partition.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/common/content_switches.h"
  #include "content/public/common/service_names.mojom.h"
  #include "content/public/common/url_constants.h"
  #include "content/public/common/web_preferences.h"
  #include "device/geolocation/access_token_store.h"
  #include "device/geolocation/geolocation_delegate.h"
  #include "net/android/network_library.h"
  #include "net/ssl/ssl_cert_request_info.h"
  #include "net/ssl/ssl_info.h"
  #include "services/service_manager/public/cpp/binder_registry.h"
  #include "storage/browser/quota/quota_settings.h"
  #include "ui/base/resource/resource_bundle.h"
  #include "ui/base/resource/resource_bundle_android.h"
  #include "ui/resources/grit/ui_resources.h"
  
  #if BUILDFLAG(ENABLE_SPELLCHECK)
  #include "components/spellcheck/browser/spellcheck_message_filter_platform.h"
  #include "components/spellcheck/common/spellcheck_switches.h"
  #endif
  
  using content::BrowserThread;
  using content::ResourceType;
  
  namespace service_manager {
  struct BindSourceInfo;
  }
  
  namespace android_webview {
  namespace {
  
  // TODO(sgurun) move this to its own file.
  // This class filters out incoming aw_contents related IPC messages for the
  // renderer process on the IPC thread.
  class AwContentsMessageFilter : public content::BrowserMessageFilter {
  public:
    explicit AwContentsMessageFilter(int process_id);
  
    // BrowserMessageFilter methods.
    void OverrideThreadForMessage(const IPC::Message& message,
                                  BrowserThread::ID* thread) override;
    bool OnMessageReceived(const IPC::Message& message) override;
  
    void OnShouldOverrideUrlLoading(int routing_id,
                                    const base::string16& url,
                                    bool has_user_gesture,
                                    bool is_redirect,
                                    bool is_main_frame,
                                    bool* ignore_navigation);
    void OnSubFrameCreated(int parent_render_frame_id, int child_render_frame_id);
  
  private:
   ~AwContentsMessageFilter() override;
  
    int process_id_;
  
    DISALLOW_COPY_AND_ASSIGN(AwContentsMessageFilter);
  };
  
  AwContentsMessageFilter::AwContentsMessageFilter(int process_id)
      : BrowserMessageFilter(AndroidWebViewMsgStart),
        process_id_(process_id) {
  }
  
  AwContentsMessageFilter::~AwContentsMessageFilter() {
  }
  
  void AwContentsMessageFilter::OverrideThreadForMessage(
      const IPC::Message& message,
      BrowserThread::ID* thread) {
    if (message.type() == AwViewHostMsg_ShouldOverrideUrlLoading::ID) {
      *thread = BrowserThread::UI;
    }
  }
  
  bool AwContentsMessageFilter::OnMessageReceived(const IPC::Message& message) {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(AwContentsMessageFilter, message)
      IPC_MESSAGE_HANDLER(AwViewHostMsg_ShouldOverrideUrlLoading,
                          OnShouldOverrideUrlLoading)
      IPC_MESSAGE_HANDLER(AwViewHostMsg_SubFrameCreated, OnSubFrameCreated)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
  }
  
  void AwContentsMessageFilter::OnShouldOverrideUrlLoading(
      int render_frame_id,
      const base::string16& url,
      bool has_user_gesture,
      bool is_redirect,
      bool is_main_frame,
      bool* ignore_navigation) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    *ignore_navigation = false;
    AwContentsClientBridge* client =
        AwContentsClientBridge::FromID(process_id_, render_frame_id);
    if (client) {
      *ignore_navigation = client->ShouldOverrideUrlLoading(
          url, has_user_gesture, is_redirect, is_main_frame);
      // If the shouldOverrideUrlLoading call caused a java exception we should
      // always return immediately here!
    } else {
      LOG(WARNING) << "Failed to find the associated render view host for url: "
                   << url;
    }
  }
  
  void AwContentsMessageFilter::OnSubFrameCreated(int parent_render_frame_id,
                                                  int child_render_frame_id) {
    AwContentsIoThreadClient::SubFrameCreated(
        process_id_, parent_render_frame_id, child_render_frame_id);
  }
  
  // A dummy binder for mojo interface autofill::mojom::PasswordManagerDriver.
  void DummyBindPasswordManagerDriver(
      const service_manager::BindSourceInfo& source_info,
      autofill::mojom::PasswordManagerDriverRequest request,
      content::RenderFrameHost* render_frame_host) {}
  
  }  // anonymous namespace
  
  // TODO(yirui): can use similar logic as in PrependToAcceptLanguagesIfNecessary
  // in chrome/browser/android/preferences/pref_service_bridge.cc
  // static
  std::string AwContentBrowserClient::GetAcceptLangsImpl() {
    // Start with the current locale(s) in BCP47 format.
    std::string locales_string = AwContents::GetLocaleList();
  
    // If accept languages do not contain en-US, add in en-US which will be
    // used with a lower q-value.
    if (locales_string.find("en-US") == std::string::npos)
      locales_string += ",en-US";
    return locales_string;
  }
  
  // static
  AwBrowserContext* AwContentBrowserClient::GetAwBrowserContext() {
    return AwBrowserContext::GetDefault();
  }
  
  AwContentBrowserClient::AwContentBrowserClient() {
    frame_interfaces_.AddInterface(
        base::Bind(&autofill::ContentAutofillDriverFactory::BindAutofillDriver));
    // Although WebView does not support password manager feature, renderer code
    // could still request this interface, so we register a dummy binder which
    // just drops the incoming request, to avoid the 'Failed to locate a binder
    // for interface' error log..
    frame_interfaces_.AddInterface(base::Bind(&DummyBindPasswordManagerDriver));
  }
  
  AwContentBrowserClient::~AwContentBrowserClient() {}
  
  AwBrowserContext* AwContentBrowserClient::InitBrowserContext() {
    base::FilePath user_data_dir;
    if (!PathService::Get(base::DIR_ANDROID_APP_DATA, &user_data_dir)) {
      NOTREACHED() << "Failed to get app data directory for Android WebView";
    }
    browser_context_.reset(new AwBrowserContext(user_data_dir));
    return browser_context_.get();
  }
  
  content::BrowserMainParts* AwContentBrowserClient::CreateBrowserMainParts(
      const content::MainFunctionParams& parameters) {
    return new AwBrowserMainParts(this);
  }
  
  content::WebContentsViewDelegate*
  AwContentBrowserClient::GetWebContentsViewDelegate(
      content::WebContents* web_contents) {
    return AwWebContentsViewDelegate::Create(web_contents);
  }
  
  void AwContentBrowserClient::RenderProcessWillLaunch(
      content::RenderProcessHost* host) {
    // Grant content: scheme access to the whole renderer process, since we impose
    // per-view access checks, and access is granted by default (see
    // AwSettings.mAllowContentUrlAccess).
    content::ChildProcessSecurityPolicy::GetInstance()->GrantScheme(
        host->GetID(), url::kContentScheme);
  
    host->AddFilter(new AwContentsMessageFilter(host->GetID()));
    host->AddFilter(new cdm::CdmMessageFilterAndroid());
    host->AddFilter(new AwPrintingMessageFilter(host->GetID()));
  
  #if BUILDFLAG(ENABLE_SPELLCHECK)
    host->AddFilter(new SpellCheckMessageFilterPlatform(host->GetID()));
  #endif
  }
  
  bool AwContentBrowserClient::IsHandledURL(const GURL& url) {
    if (!url.is_valid()) {
      // We handle error cases.
      return true;
    }
  
    const std::string scheme = url.scheme();
    DCHECK_EQ(scheme, base::ToLowerASCII(scheme));
    // See CreateJobFactory in aw_url_request_context_getter.cc for the
    // list of protocols that are handled.
    // TODO(mnaganov): Make this automatic.
    static const char* const kProtocolList[] = {
      url::kDataScheme,
      url::kBlobScheme,
      url::kFileSystemScheme,
      content::kChromeUIScheme,
      content::kChromeDevToolsScheme,
      url::kContentScheme,
    };
    if (scheme == url::kFileScheme) {
      // Return false for the "special" file URLs, so they can be loaded
      // even if access to file: scheme is not granted to the child process.
      return !IsAndroidSpecialFileUrl(url);
    }
    for (size_t i = 0; i < arraysize(kProtocolList); ++i) {
      if (scheme == kProtocolList[i])
        return true;
    }
    return net::URLRequest::IsHandledProtocol(scheme);
  }
  
  void AwContentBrowserClient::AppendExtraCommandLineSwitches(
      base::CommandLine* command_line,
      int child_process_id) {
    if (!command_line->HasSwitch(switches::kSingleProcess)) {
      // The only kind of a child process WebView can have is renderer.
      DCHECK_EQ(switches::kRendererProcess,
                command_line->GetSwitchValueASCII(switches::kProcessType));
      // Pass crash reporter enabled state to renderer processes.
      if (crash_reporter::IsCrashReporterEnabled()) {
        command_line->AppendSwitch(::switches::kEnableCrashReporter);
      }
    }
  }
  
  std::string AwContentBrowserClient::GetApplicationLocale() {
    return base::android::GetDefaultLocaleString();
  }
  
  std::string AwContentBrowserClient::GetAcceptLangs(
      content::BrowserContext* context) {
    return GetAcceptLangsImpl();
  }
  
  const gfx::ImageSkia* AwContentBrowserClient::GetDefaultFavicon() {
    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    // TODO(boliu): Bundle our own default favicon?
    return rb.GetImageSkiaNamed(IDR_DEFAULT_FAVICON);
  }
  
  bool AwContentBrowserClient::AllowAppCache(const GURL& manifest_url,
                             const GURL& first_party,
                             content::ResourceContext* context) {
    // WebView doesn't have a per-site policy for locally stored data,
    // instead AppCache can be disabled for individual WebViews.
    return true;
  }
  
  
  bool AwContentBrowserClient::AllowGetCookie(const GURL& url,
                                              const GURL& first_party,
                                              const net::CookieList& cookie_list,
                                              content::ResourceContext* context,
                                              int render_process_id,
                                              int render_frame_id) {
    return AwCookieAccessPolicy::GetInstance()->AllowGetCookie(url,
                                                               first_party,
                                                               cookie_list,
                                                               context,
                                                               render_process_id,
                                                               render_frame_id);
  }
  
  bool AwContentBrowserClient::AllowSetCookie(const GURL& url,
                                              const GURL& first_party,
                                              const std::string& cookie_line,
                                              content::ResourceContext* context,
                                              int render_process_id,
                                              int render_frame_id,
                                              const net::CookieOptions& options) {
    return AwCookieAccessPolicy::GetInstance()->AllowSetCookie(url,
                                                               first_party,
                                                               cookie_line,
                                                               context,
                                                               render_process_id,
                                                               render_frame_id,
                                                               options);
  }
  
  void AwContentBrowserClient::AllowWorkerFileSystem(
      const GURL& url,
      content::ResourceContext* context,
      const std::vector<std::pair<int, int> >& render_frames,
      base::Callback<void(bool)> callback) {
    // Android WebView does not yet support web workers.
    callback.Run(false);
  }
  
  bool AwContentBrowserClient::AllowWorkerIndexedDB(
      const GURL& url,
      const base::string16& name,
      content::ResourceContext* context,
      const std::vector<std::pair<int, int> >& render_frames) {
    // Android WebView does not yet support web workers.
    return false;
  }
  
  content::QuotaPermissionContext*
  AwContentBrowserClient::CreateQuotaPermissionContext() {
    return new AwQuotaPermissionContext;
  }
  
  void AwContentBrowserClient::GetQuotaSettings(
      content::BrowserContext* context,
      content::StoragePartition* partition,
      const storage::OptionalQuotaSettingsCallback& callback) {
    content::BrowserThread::PostTaskAndReplyWithResult(
        content::BrowserThread::FILE, FROM_HERE,
        base::Bind(&storage::CalculateNominalDynamicSettings,
                   partition->GetPath(), context->IsOffTheRecord()),
        callback);
  }
  
  void AwContentBrowserClient::AllowCertificateError(
      content::WebContents* web_contents,
      int cert_error,
      const net::SSLInfo& ssl_info,
      const GURL& request_url,
      ResourceType resource_type,
      bool overridable,
      bool strict_enforcement,
      bool expired_previous_decision,
      const base::Callback<void(content::CertificateRequestResultType)>&
          callback) {
    AwContentsClientBridge* client =
        AwContentsClientBridge::FromWebContents(web_contents);
    bool cancel_request = true;
    if (client)
      client->AllowCertificateError(cert_error,
                                    ssl_info.cert.get(),
                                    request_url,
                                    callback,
                                    &cancel_request);
    if (cancel_request)
      callback.Run(content::CERTIFICATE_REQUEST_RESULT_TYPE_DENY);
  }
  
  void AwContentBrowserClient::SelectClientCertificate(
      content::WebContents* web_contents,
      net::SSLCertRequestInfo* cert_request_info,
      net::ClientCertIdentityList client_certs,
      std::unique_ptr<content::ClientCertificateDelegate> delegate) {
    AwContentsClientBridge* client =
        AwContentsClientBridge::FromWebContents(web_contents);
    if (client)
      client->SelectClientCertificate(cert_request_info, std::move(delegate));
  }
  
  bool AwContentBrowserClient::CanCreateWindow(
      content::RenderFrameHost* opener,
      const GURL& opener_url,
      const GURL& opener_top_level_frame_url,
      const GURL& source_origin,
      content::mojom::WindowContainerType container_type,
      const GURL& target_url,
      const content::Referrer& referrer,
      const std::string& frame_name,
      WindowOpenDisposition disposition,
      const blink::mojom::WindowFeatures& features,
      bool user_gesture,
      bool opener_suppressed,
      bool* no_javascript_access) {
    // We unconditionally allow popup windows at this stage and will give
    // the embedder the opporunity to handle displaying of the popup in
    // WebContentsDelegate::AddContents (via the
    // AwContentsClient.onCreateWindow callback).
    // Note that if the embedder has blocked support for creating popup
    // windows through AwSettings, then we won't get to this point as
    // the popup creation will have been blocked at the WebKit level.
    if (no_javascript_access) {
      *no_javascript_access = false;
    }
  
    content::WebContents* web_contents =
        content::WebContents::FromRenderFrameHost(opener);
    AwSettings* settings = AwSettings::FromWebContents(web_contents);
  
    return (settings && settings->GetJavaScriptCanOpenWindowsAutomatically()) ||
           user_gesture;
  }
  
  void AwContentBrowserClient::ResourceDispatcherHostCreated() {
    AwResourceDispatcherHostDelegate::ResourceDispatcherHostCreated();
  }
  
  net::NetLog* AwContentBrowserClient::GetNetLog() {
    return browser_context_->GetAwURLRequestContext()->GetNetLog();
  }
  
  base::FilePath AwContentBrowserClient::GetDefaultDownloadDirectory() {
    // Android WebView does not currently use the Chromium downloads system.
    // Download requests are cancelled immedately when recognized; see
    // AwResourceDispatcherHost::CreateResourceHandlerForDownload. However the
    // download system still tries to start up and calls this before recognizing
    // the request has been cancelled.
    return base::FilePath();
  }
  
  std::string AwContentBrowserClient::GetDefaultDownloadName() {
    NOTREACHED() << "Android WebView does not use chromium downloads";
    return std::string();
  }
  
  void AwContentBrowserClient::DidCreatePpapiPlugin(
      content::BrowserPpapiHost* browser_host) {
    NOTREACHED() << "Android WebView does not support plugins";
  }
  
  bool AwContentBrowserClient::AllowPepperSocketAPI(
      content::BrowserContext* browser_context,
      const GURL& url,
      bool private_api,
      const content::SocketPermissionRequest* params) {
    NOTREACHED() << "Android WebView does not support plugins";
    return false;
  }
  
  bool AwContentBrowserClient::IsPepperVpnProviderAPIAllowed(
      content::BrowserContext* browser_context,
      const GURL& url) {
    NOTREACHED() << "Android WebView does not support plugins";
    return false;
  }
  
  content::TracingDelegate* AwContentBrowserClient::GetTracingDelegate() {
    return new AwTracingDelegate();
  }
  
  void AwContentBrowserClient::GetAdditionalMappedFilesForChildProcess(
        const base::CommandLine& command_line,
        int child_process_id,
        content::FileDescriptorInfo* mappings) {
    base::MemoryMappedFile::Region region;
    int fd = ui::GetMainAndroidPackFd(&region);
    mappings->ShareWithRegion(kAndroidWebViewMainPakDescriptor, fd, region);
  
    fd = ui::GetCommonResourcesPackFd(&region);
    mappings->ShareWithRegion(kAndroidWebView100PercentPakDescriptor, fd, region);
  
    fd = ui::GetLocalePackFd(&region);
    mappings->ShareWithRegion(kAndroidWebViewLocalePakDescriptor, fd, region);
  
    breakpad::CrashDumpObserver::GetInstance()->BrowserChildProcessStarted(
        child_process_id, mappings);
  }
  
  void AwContentBrowserClient::OverrideWebkitPrefs(
      content::RenderViewHost* rvh,
      content::WebPreferences* web_prefs) {
    AwSettings* aw_settings = AwSettings::FromWebContents(
        content::WebContents::FromRenderViewHost(rvh));
    if (aw_settings) {
      aw_settings->PopulateWebPreferences(web_prefs);
    }
  }
  
  std::vector<std::unique_ptr<content::NavigationThrottle>>
  AwContentBrowserClient::CreateThrottlesForNavigation(
      content::NavigationHandle* navigation_handle) {
    std::vector<std::unique_ptr<content::NavigationThrottle>> throttles;
    // We allow intercepting only navigations within main frames. This
    // is used to post onPageStarted. We handle shouldOverrideUrlLoading
    // via a sync IPC.
    if (navigation_handle->IsInMainFrame()) {
      throttles.push_back(
          navigation_interception::InterceptNavigationDelegate::CreateThrottleFor(
              navigation_handle));
    }
    return throttles;
  }
  
  content::DevToolsManagerDelegate*
  AwContentBrowserClient::GetDevToolsManagerDelegate() {
    return new AwDevToolsManagerDelegate();
  }
  
  std::unique_ptr<base::Value> AwContentBrowserClient::GetServiceManifestOverlay(
      base::StringPiece name) {
    int id = -1;
    if (name == content::mojom::kBrowserServiceName)
      id = IDR_AW_BROWSER_MANIFEST_OVERLAY;
    else if (name == content::mojom::kRendererServiceName)
      id = IDR_AW_RENDERER_MANIFEST_OVERLAY;
    if (id == -1)
      return nullptr;
  
    base::StringPiece manifest_contents =
        ui::ResourceBundle::GetSharedInstance().GetRawDataResourceForScale(
            id, ui::ScaleFactor::SCALE_FACTOR_NONE);
    return base::JSONReader::Read(manifest_contents);
  }
  
  void AwContentBrowserClient::BindInterfaceRequestFromFrame(
      content::RenderFrameHost* render_frame_host,
      const service_manager::BindSourceInfo& source_info,
      const std::string& interface_name,
      mojo::ScopedMessagePipeHandle interface_pipe) {
    if (frame_interfaces_.CanBindInterface(interface_name)) {
      frame_interfaces_.BindInterface(source_info, interface_name,
                                      std::move(interface_pipe),
                                      render_frame_host);
    }
  }
  
  }  // namespace android_webview
  