chromium/fuchsia_web/webengine/browser/web_engine_content_browser_client.cc

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

#include "fuchsia_web/webengine/browser/web_engine_content_browser_client.h"

#include <string>
#include <utility>

#include "base/command_line.h"
#include "base/functional/callback.h"
#include "base/i18n/rtl.h"
#include "base/strings/string_split.h"
#include "build/chromecast_buildflags.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/policy/content/safe_sites_navigation_throttle.h"
#include "components/site_isolation/features.h"
#include "components/site_isolation/preloaded_isolated_origins.h"
#include "components/strings/grit/components_locale_settings.h"
#include "components/url_rewrite/common/url_loader_throttle.h"
#include "components/url_rewrite/common/url_request_rewrite_rules.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/devtools_manager_delegate.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/common/content_switches.h"
#include "fuchsia_web/common/fuchsia_dir_scheme.h"
#include "fuchsia_web/common/init_logging.h"
#include "fuchsia_web/webengine/browser/frame_impl.h"
#include "fuchsia_web/webengine/browser/navigation_policy_throttle.h"
#include "fuchsia_web/webengine/browser/web_engine_browser_context.h"
#include "fuchsia_web/webengine/browser/web_engine_browser_interface_binders.h"
#include "fuchsia_web/webengine/browser/web_engine_browser_main_parts.h"
#include "fuchsia_web/webengine/browser/web_engine_devtools_controller.h"
#include "fuchsia_web/webengine/common/cors_exempt_headers.h"
#include "fuchsia_web/webengine/common/web_engine_content_client.h"
#include "fuchsia_web/webengine/switches.h"
#include "media/base/media_switches.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_util.h"
#include "net/ssl/ssl_private_key.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "third_party/blink/public/common/switches.h"
#include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom.h"
#include "third_party/widevine/cdm/buildflags.h"
#include "ui/base/l10n/l10n_util.h"

namespace {

constexpr net::NetworkTrafficAnnotationTag kProxyConfigTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("webview_proxy_config", R"(
      semantics {
        sender: "Proxy configuration via a command line flag"
        description:
          "Used to fetch HTTP/HTTPS/SOCKS5/PAC proxy configuration when "
          "proxy is configured by the --proxy-server command line flag. "
          "When proxy implies automatic configuration, it can send network "
          "requests in the scope of this annotation."
        trigger:
          "Whenever a network request is made when the system proxy settings "
          "are used, and they indicate to use a proxy server."
        data:
          "Proxy configuration."
        destination: OTHER
        destination_other: "The proxy server specified in the configuration."
      }
      policy {
        cookies_allowed: NO
        setting:
          "This request cannot be disabled in settings. However it will never "
          "be made if user does not run with the '--proxy-server' switch."
        policy_exception_justification:
          "Not implemented, behaviour only available behind a switch."
      })");

class DevToolsManagerDelegate final : public content::DevToolsManagerDelegate {
 public:
  explicit DevToolsManagerDelegate(WebEngineBrowserMainParts* main_parts)
      : main_parts_(main_parts) {
    DCHECK(main_parts_);
  }
  ~DevToolsManagerDelegate() override = default;

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

  // content::DevToolsManagerDelegate implementation.
  std::vector<content::BrowserContext*> GetBrowserContexts() override {
    return main_parts_->browser_contexts();
  }
  content::BrowserContext* GetDefaultBrowserContext() override {
    std::vector<content::BrowserContext*> contexts = GetBrowserContexts();
    return contexts.empty() ? nullptr : contexts.front();
  }
  content::DevToolsAgentHost::List RemoteDebuggingTargets(
      DevToolsManagerDelegate::TargetType target_type) override {
    LOG_IF(WARNING, target_type != DevToolsManagerDelegate::kFrame)
        << "Ignoring unsupported remote target type: " << target_type;
    return main_parts_->devtools_controller()->RemoteDebuggingTargets();
  }

 private:
  WebEngineBrowserMainParts* const main_parts_;
};

std::vector<std::string> GetCorsExemptHeaders() {
  return base::SplitString(
      base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
          switches::kCorsExemptHeaders),
      ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
}

static constexpr char const* kRendererSwitchesToCopy[] = {
    blink::switches::kSharedArrayBufferAllowedOrigins,
    switches::kCorsExemptHeaders,
    switches::kEnableCastStreamingReceiver,
    switches::kEnableProtectedVideoBuffers,
    switches::kForceProtectedVideoOutputBuffers,
    switches::kMinVideoDecoderOutputBufferSize,

// TODO(crbug.com/42050020): Delete these two switches when fixed.
#if BUILDFLAG(ENABLE_WIDEVINE)
    switches::kEnableWidevine,
#if BUILDFLAG(ENABLE_CAST_RECEIVER)
    switches::kPlayreadyKeySystem,
#endif
#endif

    // Pass to the renderer process for consistency with Chrome.
    network::switches::kUnsafelyTreatInsecureOriginAsSecure,
};

static constexpr char const*
    kUnsafelyTreatInsecureOriginAsSecureSwitchToCopy[] = {
        network::switches::kUnsafelyTreatInsecureOriginAsSecure,
};

// These are passed to every child process and should only be used when it is
// not possible to narrow the scope down to a subset of processes.
static constexpr char const* kAllProcessSwitchesToCopy[] = {
    // This is used by every child process in WebEngineContentClient.
    switches::kEnableContentDirectories,
};

}  // namespace

WebEngineContentBrowserClient::WebEngineContentBrowserClient()
    : cors_exempt_headers_(GetCorsExemptHeaders()) {
  // Logging in this class ensures this is logged once per web_instance.
  LogComponentStartWithVersion("WebEngine web_instance");
}

WebEngineContentBrowserClient::~WebEngineContentBrowserClient() = default;

std::unique_ptr<content::BrowserMainParts>
WebEngineContentBrowserClient::CreateBrowserMainParts(
    bool /* is_integration_test */) {
  auto browser_main_parts = std::make_unique<WebEngineBrowserMainParts>(this);
  main_parts_ = browser_main_parts.get();
  return browser_main_parts;
}

std::unique_ptr<content::DevToolsManagerDelegate>
WebEngineContentBrowserClient::CreateDevToolsManagerDelegate() {
  DCHECK(main_parts_);
  return std::make_unique<DevToolsManagerDelegate>(main_parts_);
}

std::string WebEngineContentBrowserClient::GetProduct() {
  return std::string(version_info::GetProductNameAndVersionForUserAgent());
}

std::string WebEngineContentBrowserClient::GetUserAgent() {
  std::string user_agent = embedder_support::GetUserAgent();
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kUserAgentProductAndVersion)) {
    user_agent +=
        " " + base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
                  switches::kUserAgentProductAndVersion);
  }
  return user_agent;
}

blink::UserAgentMetadata WebEngineContentBrowserClient::GetUserAgentMetadata() {
  return embedder_support::GetUserAgentMetadata();
}

void WebEngineContentBrowserClient::OverrideWebkitPrefs(
    content::WebContents* web_contents,
    blink::web_pref::WebPreferences* web_prefs) {
  // Disable WebSQL support since it is being removed from the web platform
  // and does not work. See crbug.com/1317431.
  web_prefs->databases_enabled = false;

  // TODO(crbug.com/40245916): Remove once supported in WebEngine.
  web_prefs->disable_webauthn = true;

#if BUILDFLAG(ENABLE_CAST_RECEIVER)
  static bool allow_insecure_content =
      base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kAllowRunningInsecureContent);
  if (allow_insecure_content) {
    web_prefs->allow_running_insecure_content = true;
  }
#endif

  FrameImpl* frame = FrameImpl::FromWebContents(web_contents);
  // This method may be called when a |web_contents| is instantiated but an
  // associated frame has not been created.
  if (frame != nullptr) {
    frame->OverrideWebPreferences(web_prefs);
  }
}

void WebEngineContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
    content::RenderFrameHost* render_frame_host,
    mojo::BinderMapWithContext<content::RenderFrameHost*>* map) {
  PopulateFuchsiaFrameBinders(map);
}

mojo::PendingRemote<network::mojom::URLLoaderFactory>
WebEngineContentBrowserClient::CreateNonNetworkNavigationURLLoaderFactory(
    const std::string& scheme,
    int frame_tree_node_id) {
  if (scheme == kFuchsiaDirScheme) {
    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kEnableContentDirectories)) {
      return ContentDirectoryLoaderFactory::Create();
    }
  }
  return {};
}

void WebEngineContentBrowserClient::
    RegisterNonNetworkSubresourceURLLoaderFactories(
        int render_process_id,
        int render_frame_id,
        const std::optional<url::Origin>& request_initiator_origin,
        NonNetworkURLLoaderFactoryMap* factories) {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kEnableContentDirectories)) {
    factories->emplace(kFuchsiaDirScheme,
                       ContentDirectoryLoaderFactory::Create());
  }
}

bool WebEngineContentBrowserClient::ShouldEnableStrictSiteIsolation() {
  static BASE_FEATURE(kSitePerProcess, "site-per-process",
                      base::FEATURE_ENABLED_BY_DEFAULT);
  static bool enable_strict_isolation =
      base::FeatureList::IsEnabled(kSitePerProcess);
  return enable_strict_isolation;
}

void WebEngineContentBrowserClient::AppendExtraCommandLineSwitches(
    base::CommandLine* command_line,
    int child_process_id) {
  const base::CommandLine& browser_command_line =
      *base::CommandLine::ForCurrentProcess();

  command_line->CopySwitchesFrom(browser_command_line,
                                 kAllProcessSwitchesToCopy);

  std::string process_type =
      command_line->GetSwitchValueASCII(switches::kProcessType);

  if (process_type == switches::kRendererProcess) {
    command_line->CopySwitchesFrom(browser_command_line,
                                   kRendererSwitchesToCopy);
  } else if (process_type == switches::kUtilityProcess) {
    // Although only the Network process needs
    // kUnsafelyTreatInsecureOriginAsSecureSwitchToCopy, differentiating utility
    // process sub-types is non-trivial. ChromeContentBrowserClient appends this
    // switch to all Utility processes so do the same here.
    // Do not add other switches here.
    command_line->CopySwitchesFrom(
        browser_command_line, kUnsafelyTreatInsecureOriginAsSecureSwitchToCopy);
  }
}

std::string WebEngineContentBrowserClient::GetApplicationLocale() {
  // ICU is configured with the system locale by WebEngineBrowserMainParts.
  return base::i18n::GetConfiguredLocale();
}

std::string WebEngineContentBrowserClient::GetAcceptLangs(
    content::BrowserContext* context) {
  // Returns a comma-separated list of language codes, in preference order.
  // This is passed to net::HttpUtil::GenerateAcceptLanguageHeader() to
  // generate a legacy "accept-language" header value.
  return l10n_util::GetStringUTF8(IDS_ACCEPT_LANGUAGES);
}

base::OnceClosure WebEngineContentBrowserClient::SelectClientCertificate(
    content::BrowserContext* browser_context,
    int process_id,
    content::WebContents* web_contents,
    net::SSLCertRequestInfo* cert_request_info,
    net::ClientCertIdentityList client_certs,
    std::unique_ptr<content::ClientCertificateDelegate> delegate) {
  // Continue without a certificate.
  delegate->ContinueWithCertificate(nullptr, nullptr);
  return base::OnceClosure();
}

std::vector<std::unique_ptr<content::NavigationThrottle>>
WebEngineContentBrowserClient::CreateThrottlesForNavigation(
    content::NavigationHandle* navigation_handle) {
  std::vector<std::unique_ptr<content::NavigationThrottle>> throttles;
  auto* frame_impl =
      FrameImpl::FromWebContents(navigation_handle->GetWebContents());
  DCHECK(frame_impl);

  // Only create throttle if FrameImpl has a NavigationPolicyProvider,
  // indicating an interest in navigations.
  if (frame_impl->navigation_policy_handler()) {
    throttles.push_back(std::make_unique<NavigationPolicyThrottle>(
        navigation_handle, frame_impl->navigation_policy_handler()));
  }

  const std::optional<std::string>& explicit_sites_filter_error_page =
      frame_impl->explicit_sites_filter_error_page();

  if (explicit_sites_filter_error_page) {
    throttles.push_back(std::make_unique<SafeSitesNavigationThrottle>(
        navigation_handle,
        navigation_handle->GetWebContents()->GetBrowserContext(),
        *explicit_sites_filter_error_page));
  }

  return throttles;
}

std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
WebEngineContentBrowserClient::CreateURLLoaderThrottles(
    const network::ResourceRequest& request,
    content::BrowserContext* browser_context,
    const base::RepeatingCallback<content::WebContents*()>& wc_getter,
    content::NavigationUIData* navigation_ui_data,
    int frame_tree_node_id,
    std::optional<int64_t> navigation_id) {
  if (frame_tree_node_id == content::RenderFrameHost::kNoFrameTreeNodeId) {
    // TODO(crbug.com/40244093): Add support for Shared and Service Workers.
    return {};
  }

  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
  auto* frame_impl = FrameImpl::FromWebContents(wc_getter.Run());
  DCHECK(frame_impl);
  auto rules =
      frame_impl->url_request_rewrite_rules_manager()->GetCachedRules();
  if (rules) {
    throttles.emplace_back(std::make_unique<url_rewrite::URLLoaderThrottle>(
        rules, base::BindRepeating(&IsHeaderCorsExempt)));
  }
  return throttles;
}

void WebEngineContentBrowserClient::ConfigureNetworkContextParams(
    content::BrowserContext* context,
    bool in_memory,
    const base::FilePath& relative_partition_path,
    network::mojom::NetworkContextParams* network_context_params,
    cert_verifier::mojom::CertVerifierCreationParams*
        cert_verifier_creation_params) {
  const base::CommandLine& command_line =
      *base::CommandLine::ForCurrentProcess();
  std::string proxy = command_line.GetSwitchValueASCII(switches::kProxyServer);
  if (!proxy.empty()) {
    net::ProxyConfig proxy_config;
    proxy_config.proxy_rules().ParseFromString(proxy);
    std::string bypass_list =
        command_line.GetSwitchValueASCII(switches::kProxyBypassList);
    if (!bypass_list.empty()) {
      proxy_config.proxy_rules().bypass_rules.ParseFromString(bypass_list);
    }

    network_context_params->initial_proxy_config =
        net::ProxyConfigWithAnnotation(proxy_config,
                                       kProxyConfigTrafficAnnotation);
  }

  network_context_params->user_agent = GetUserAgent();
  network_context_params->accept_language =
      net::HttpUtil::GenerateAcceptLanguageHeader(GetAcceptLangs(context));

  // Set the list of cors_exempt_headers which may be specified in a URLRequest,
  // starting with the headers passed in via
  // |CreateContextParams.cors_exempt_headers|.
  network_context_params->cors_exempt_header_list = cors_exempt_headers_;
}

std::vector<url::Origin>
WebEngineContentBrowserClient::GetOriginsRequiringDedicatedProcess() {
  std::vector<url::Origin> isolated_origin_list;

  // Include additional origins preloaded with specific browser configurations,
  // if any.
  auto built_in_origins =
      site_isolation::GetBrowserSpecificBuiltInIsolatedOrigins();
  std::move(std::begin(built_in_origins), std::end(built_in_origins),
            std::back_inserter(isolated_origin_list));

  return isolated_origin_list;
}