text: // Copyright (c) 2013 The Chromium Embedded Framework Authors.
  // Portions 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 "libcef/renderer/alloy/alloy_content_renderer_client.h"
  
  #include <utility>
  
  #include "base/compiler_specific.h"
  
  // Enable deprecation warnings on Windows. See http://crbug.com/585142.
  #if defined(OS_WIN)
  #if defined(__clang__)
  #pragma GCC diagnostic push
  #pragma GCC diagnostic error "-Wdeprecated-declarations"
  #else
  #pragma warning(push)
  #pragma warning(default : 4996)
  #endif
  #endif
  
  #include "libcef/browser/alloy/alloy_content_browser_client.h"
  #include "libcef/browser/context.h"
  #include "libcef/common/alloy/alloy_content_client.h"
  #include "libcef/common/app_manager.h"
  #include "libcef/common/cef_switches.h"
  #include "libcef/common/extensions/extensions_client.h"
  #include "libcef/common/extensions/extensions_util.h"
  #include "libcef/common/request_impl.h"
  #include "libcef/features/runtime_checks.h"
  #include "libcef/renderer/alloy/alloy_render_thread_observer.h"
  #include "libcef/renderer/alloy/url_loader_throttle_provider_impl.h"
  #include "libcef/renderer/browser_impl.h"
  #include "libcef/renderer/extensions/extensions_renderer_client.h"
  #include "libcef/renderer/extensions/print_render_frame_helper_delegate.h"
  #include "libcef/renderer/render_frame_observer.h"
  #include "libcef/renderer/render_manager.h"
  #include "libcef/renderer/thread_util.h"
  
  #include "base/command_line.h"
  #include "base/macros.h"
  #include "base/memory/ptr_util.h"
  #include "base/metrics/user_metrics_action.h"
  #include "base/path_service.h"
  #include "base/stl_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/task/post_task.h"
  #include "build/build_config.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/common/url_constants.h"
  #include "chrome/renderer/browser_exposed_renderer_interfaces.h"
  #include "chrome/renderer/chrome_content_renderer_client.h"
  #include "chrome/renderer/extensions/chrome_extensions_renderer_client.h"
  #include "chrome/renderer/loadtimes_extension_bindings.h"
  #include "chrome/renderer/media/chrome_key_systems.h"
  #include "chrome/renderer/pepper/chrome_pdf_print_client.h"
  #include "chrome/renderer/pepper/pepper_helper.h"
  #include "chrome/renderer/plugins/chrome_plugin_placeholder.h"
  #include "components/content_settings/core/common/content_settings_types.h"
  #include "components/nacl/common/nacl_constants.h"
  #include "components/printing/renderer/print_render_frame_helper.h"
  #include "components/spellcheck/renderer/spellcheck.h"
  #include "components/spellcheck/renderer/spellcheck_provider.h"
  #include "components/visitedlink/renderer/visitedlink_reader.h"
  #include "components/web_cache/renderer/web_cache_impl.h"
  #include "content/common/frame_messages.h"
  #include "content/public/browser/browser_task_traits.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/render_process_host.h"
  #include "content/public/child/child_thread.h"
  #include "content/public/common/content_constants.h"
  #include "content/public/common/content_paths.h"
  #include "content/public/common/content_switches.h"
  #include "content/public/common/url_constants.h"
  #include "content/public/renderer/render_view.h"
  #include "content/public/renderer/render_view_visitor.h"
  #include "extensions/common/switches.h"
  #include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h"
  #include "extensions/renderer/renderer_extension_registry.h"
  #include "ipc/ipc_sync_channel.h"
  #include "media/base/media.h"
  #include "mojo/public/cpp/bindings/binder_map.h"
  #include "mojo/public/cpp/bindings/generic_pending_receiver.h"
  #include "printing/print_settings.h"
  #include "services/network/public/cpp/is_potentially_trustworthy.h"
  #include "services/service_manager/public/cpp/connector.h"
  #include "services/service_manager/public/cpp/interface_provider.h"
  #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
  #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
  #include "third_party/blink/public/platform/scheduler/web_renderer_process_type.h"
  #include "third_party/blink/public/platform/url_conversion.h"
  #include "third_party/blink/public/platform/web_runtime_features.h"
  #include "third_party/blink/public/platform/web_string.h"
  #include "third_party/blink/public/platform/web_url.h"
  #include "third_party/blink/public/web/web_console_message.h"
  #include "third_party/blink/public/web/web_element.h"
  #include "third_party/blink/public/web/web_frame.h"
  #include "third_party/blink/public/web/web_local_frame.h"
  #include "third_party/blink/public/web/web_security_policy.h"
  #include "third_party/blink/public/web/web_view.h"
  #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
  #include "ui/base/l10n/l10n_util.h"
  
  #if defined(OS_MAC)
  #include "base/mac/mac_util.h"
  #include "base/strings/sys_string_conversions.h"
  #endif
  
  namespace {
  
  bool IsStandaloneExtensionProcess() {
    return extensions::ExtensionsEnabled() &&
           extensions::CefExtensionsRendererClient::
               IsStandaloneExtensionProcess();
  }
  
  }  // namespace
  
  AlloyContentRendererClient::AlloyContentRendererClient()
      : main_entry_time_(base::TimeTicks::Now()),
        render_manager_(new CefRenderManager) {
    if (extensions::ExtensionsEnabled()) {
      extensions_client_.reset(new extensions::CefExtensionsClient);
      extensions::ExtensionsClient::Set(extensions_client_.get());
      extensions_renderer_client_.reset(
          new extensions::CefExtensionsRendererClient);
      extensions::ExtensionsRendererClient::Set(
          extensions_renderer_client_.get());
    }
  }
  
  AlloyContentRendererClient::~AlloyContentRendererClient() {}
  
  // static
  AlloyContentRendererClient* AlloyContentRendererClient::Get() {
    REQUIRE_ALLOY_RUNTIME();
    return static_cast<AlloyContentRendererClient*>(
        CefAppManager::Get()->GetContentClient()->renderer());
  }
  
  scoped_refptr<base::SingleThreadTaskRunner>
  AlloyContentRendererClient::GetCurrentTaskRunner() {
    // Check if currently on the render thread.
    if (CEF_CURRENTLY_ON_RT())
      return render_task_runner_;
    return nullptr;
  }
  
  void AlloyContentRendererClient::RunSingleProcessCleanup() {
    DCHECK(content::RenderProcessHost::run_renderer_in_process());
  
    // Make sure the render thread was actually started.
    if (!render_task_runner_.get())
      return;
  
    if (content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
      RunSingleProcessCleanupOnUIThread();
    } else {
      base::PostTask(
          FROM_HERE, {content::BrowserThread::UI},
          base::Bind(
              &AlloyContentRendererClient::RunSingleProcessCleanupOnUIThread,
              base::Unretained(this)));
    }
  
    // Wait for the render thread cleanup to complete. Spin instead of using
    // base::WaitableEvent because calling Wait() is not allowed on the UI
    // thread.
    bool complete = false;
    do {
      {
        base::AutoLock lock_scope(single_process_cleanup_lock_);
        complete = single_process_cleanup_complete_;
      }
      if (!complete)
        base::PlatformThread::YieldCurrentThread();
    } while (!complete);
  }
  
  void AlloyContentRendererClient::PostIOThreadCreated(
      base::SingleThreadTaskRunner*) {
    // TODO(cef): Enable these once the implementation supports it.
    blink::WebRuntimeFeatures::EnableNotifications(false);
    blink::WebRuntimeFeatures::EnablePushMessaging(false);
    blink::RuntimeEnabledFeatures::SetBadgingEnabled(false);
  }
  
  void AlloyContentRendererClient::RenderThreadStarted() {
    const base::CommandLine* command_line =
        base::CommandLine::ForCurrentProcess();
  
    render_task_runner_ = base::ThreadTaskRunnerHandle::Get();
    observer_ = std::make_unique<AlloyRenderThreadObserver>();
    web_cache_impl_ = std::make_unique<web_cache::WebCacheImpl>();
    visited_link_slave_ = std::make_unique<visitedlink::VisitedLinkReader>();
  
    content::RenderThread* thread = content::RenderThread::Get();
  
    thread->SetRendererProcessType(
        IsStandaloneExtensionProcess()
            ? blink::scheduler::WebRendererProcessType::kExtensionRenderer
            : blink::scheduler::WebRendererProcessType::kRenderer);
  
    thread->AddObserver(observer_.get());
  
    if (!command_line->HasSwitch(switches::kDisableSpellChecking)) {
      spellcheck_ = std::make_unique<SpellCheck>(this);
    }
  
    if (content::RenderProcessHost::run_renderer_in_process()) {
      // When running in single-process mode register as a destruction observer
      // on the render thread's MessageLoop.
      base::CurrentThread::Get()->AddDestructionObserver(this);
    }
  
  #if defined(OS_MAC)
    {
      base::ScopedCFTypeRef<CFStringRef> key(
          base::SysUTF8ToCFStringRef("NSScrollViewRubberbanding"));
      base::ScopedCFTypeRef<CFStringRef> value;
  
      // If the command-line switch is specified then set the value that will be
      // checked in RenderThreadImpl::Init(). Otherwise, remove the application-
      // level value.
      if (command_line->HasSwitch(switches::kDisableScrollBounce))
        value.reset(base::SysUTF8ToCFStringRef("false"));
  
      CFPreferencesSetAppValue(key, value, kCFPreferencesCurrentApplication);
      CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication);
    }
  #endif  // defined(OS_MAC)
  
    if (extensions::PdfExtensionEnabled()) {
      pdf_print_client_.reset(new ChromePDFPrintClient());
      pdf::PepperPDFHost::SetPrintClient(pdf_print_client_.get());
    }
  
    if (extensions::ExtensionsEnabled())
      extensions_renderer_client_->RenderThreadStarted();
  }
  
  void AlloyContentRendererClient::ExposeInterfacesToBrowser(
      mojo::BinderMap* binders) {
    auto task_runner = base::SequencedTaskRunnerHandle::Get();
  
    binders->Add(base::BindRepeating(&web_cache::WebCacheImpl::BindReceiver,
                                     base::Unretained(web_cache_impl_.get())),
                 task_runner);
  
    binders->Add(visited_link_slave_->GetBindCallback(), task_runner);
  
    if (spellcheck_) {
      binders->Add(
          base::BindRepeating(
              [](SpellCheck* spellcheck,
                 mojo::PendingReceiver<spellcheck::mojom::SpellChecker>
                     receiver) { spellcheck->BindReceiver(std::move(receiver)); },
              base::Unretained(spellcheck_.get())),
          task_runner);
    }
  
    render_manager_->ExposeInterfacesToBrowser(binders);
  }
  
  void AlloyContentRendererClient::RenderThreadConnected() {
    // Register extensions last because it will trigger WebKit initialization.
    content::RenderThread* thread = content::RenderThread::Get();
    thread->RegisterExtension(extensions_v8::LoadTimesExtension::Get());
  
    render_manager_->RenderThreadConnected();
  }
  
  void AlloyContentRendererClient::RenderFrameCreated(
      content::RenderFrame* render_frame) {
    auto render_frame_observer = new CefRenderFrameObserver(render_frame);
  
    new PepperHelper(render_frame);
  
    if (extensions::ExtensionsEnabled()) {
      extensions_renderer_client_->RenderFrameCreated(
          render_frame, render_frame_observer->registry());
  
      render_frame_observer->associated_interfaces()->AddInterface(
          base::BindRepeating(
              &extensions::MimeHandlerViewContainerManager::BindReceiver,
              render_frame->GetRoutingID()));
    }
  
    const base::CommandLine* command_line =
        base::CommandLine::ForCurrentProcess();
    if (!command_line->HasSwitch(switches::kDisableSpellChecking)) {
      new SpellCheckProvider(render_frame, spellcheck_.get(), this);
    }
  
    bool browser_created;
    base::Optional<bool> is_windowless;
    render_manager_->RenderFrameCreated(render_frame, render_frame_observer,
                                        browser_created, is_windowless);
    if (browser_created) {
      OnBrowserCreated(render_frame->GetRenderView(), is_windowless);
    }
  
    if (is_windowless.has_value()) {
      new printing::PrintRenderFrameHelper(
          render_frame,
          base::WrapUnique(
              new extensions::CefPrintRenderFrameHelperDelegate(*is_windowless)));
    }
  }
  
  void AlloyContentRendererClient::RenderViewCreated(
      content::RenderView* render_view) {
    bool browser_created;
    base::Optional<bool> is_windowless;
    render_manager_->RenderViewCreated(render_view, browser_created,
                                       is_windowless);
    if (browser_created) {
      OnBrowserCreated(render_view, is_windowless);
    }
  }
  
  bool AlloyContentRendererClient::IsPluginHandledExternally(
      content::RenderFrame* render_frame,
      const blink::WebElement& plugin_element,
      const GURL& original_url,
      const std::string& mime_type) {
    if (!extensions::ExtensionsEnabled())
      return false;
  
    DCHECK(plugin_element.HasHTMLTagName("object") ||
           plugin_element.HasHTMLTagName("embed"));
    // Blink will next try to load a WebPlugin which would end up in
    // OverrideCreatePlugin, sending another IPC only to find out the plugin is
    // not supported. Here it suffices to return false but there should perhaps be
    // a more unified approach to avoid sending the IPC twice.
    chrome::mojom::PluginInfoPtr plugin_info = chrome::mojom::PluginInfo::New();
    ChromeContentRendererClient::GetPluginInfoHost()->GetPluginInfo(
        render_frame->GetRoutingID(), original_url,
        render_frame->GetWebFrame()->Top()->GetSecurityOrigin(), mime_type,
        &plugin_info);
    // TODO(ekaramad): Not continuing here due to a disallowed status should take
    // us to CreatePlugin. See if more in depths investigation of |status| is
    // necessary here (see https://crbug.com/965747). For now, returning false
    // should take us to CreatePlugin after HTMLPlugInElement which is called
    // through HTMLPlugInElement::LoadPlugin code path.
    if (plugin_info->status != chrome::mojom::PluginStatus::kAllowed &&
        plugin_info->status !=
            chrome::mojom::PluginStatus::kPlayImportantContent) {
      // We could get here when a MimeHandlerView is loaded inside a <webview>
      // which is using permissions API (see WebViewPluginTests).
      ChromeExtensionsRendererClient::DidBlockMimeHandlerViewForDisallowedPlugin(
          plugin_element);
      return false;
    }
    return ChromeExtensionsRendererClient::MaybeCreateMimeHandlerView(
        plugin_element, original_url, plugin_info->actual_mime_type,
        plugin_info->plugin);
  }
  
  bool AlloyContentRendererClient::OverrideCreatePlugin(
      content::RenderFrame* render_frame,
      const blink::WebPluginParams& params,
      blink::WebPlugin** plugin) {
    std::string orig_mime_type = params.mime_type.Utf8();
    if (extensions::ExtensionsEnabled() &&
        !extensions_renderer_client_->OverrideCreatePlugin(render_frame,
                                                           params)) {
      return false;
    }
  
    GURL url(params.url);
    chrome::mojom::PluginInfoPtr plugin_info = chrome::mojom::PluginInfo::New();
    ChromeContentRendererClient::GetPluginInfoHost()->GetPluginInfo(
        render_frame->GetRoutingID(), url,
        render_frame->GetWebFrame()->Top()->GetSecurityOrigin(), orig_mime_type,
        &plugin_info);
    *plugin = ChromeContentRendererClient::CreatePlugin(render_frame, params,
                                                        *plugin_info);
    return true;
  }
  
  void AlloyContentRendererClient::WillSendRequest(
      blink::WebLocalFrame* frame,
      ui::PageTransition transition_type,
      const blink::WebURL& url,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin* initiator_origin,
      GURL* new_url) {
    if (extensions::ExtensionsEnabled()) {
      extensions_renderer_client_->WillSendRequest(frame, transition_type, url,
                                                   site_for_cookies,
                                                   initiator_origin, new_url);
      if (!new_url->is_empty())
        return;
    }
  }
  
  uint64_t AlloyContentRendererClient::VisitedLinkHash(const char* canonical_url,
                                                       size_t length) {
    return visited_link_slave_->ComputeURLFingerprint(canonical_url, length);
  }
  
  bool AlloyContentRendererClient::IsLinkVisited(uint64_t link_hash) {
    return visited_link_slave_->IsVisited(link_hash);
  }
  
  bool AlloyContentRendererClient::IsOriginIsolatedPepperPlugin(
      const base::FilePath& plugin_path) {
    // Isolate all the plugins (including the PDF plugin).
    return true;
  }
  
  void AlloyContentRendererClient::AddSupportedKeySystems(
      std::vector<std::unique_ptr<::media::KeySystemProperties>>* key_systems) {
    AddChromeKeySystems(key_systems);
  }
  
  void AlloyContentRendererClient::RunScriptsAtDocumentStart(
      content::RenderFrame* render_frame) {
    if (extensions::ExtensionsEnabled())
      extensions_renderer_client_->RunScriptsAtDocumentStart(render_frame);
  }
  
  void AlloyContentRendererClient::RunScriptsAtDocumentEnd(
      content::RenderFrame* render_frame) {
    if (extensions::ExtensionsEnabled())
      extensions_renderer_client_->RunScriptsAtDocumentEnd(render_frame);
  }
  
  void AlloyContentRendererClient::RunScriptsAtDocumentIdle(
      content::RenderFrame* render_frame) {
    if (extensions::ExtensionsEnabled())
      extensions_renderer_client_->RunScriptsAtDocumentIdle(render_frame);
  }
  
  void AlloyContentRendererClient::DevToolsAgentAttached() {
    // WebWorkers may be creating agents on a different thread.
    if (!render_task_runner_->BelongsToCurrentThread()) {
      render_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&AlloyContentRendererClient::DevToolsAgentAttached,
                         base::Unretained(this)));
      return;
    }
  
    render_manager_->DevToolsAgentAttached();
  }
  
  void AlloyContentRendererClient::DevToolsAgentDetached() {
    // WebWorkers may be creating agents on a different thread.
    if (!render_task_runner_->BelongsToCurrentThread()) {
      render_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&AlloyContentRendererClient::DevToolsAgentDetached,
                         base::Unretained(this)));
      return;
    }
  
    render_manager_->DevToolsAgentDetached();
  }
  
  std::unique_ptr<blink::URLLoaderThrottleProvider>
  AlloyContentRendererClient::CreateURLLoaderThrottleProvider(
      blink::URLLoaderThrottleProviderType provider_type) {
    return std::make_unique<CefURLLoaderThrottleProviderImpl>(provider_type);
  }
  
  void AlloyContentRendererClient::GetInterface(
      const std::string& interface_name,
      mojo::ScopedMessagePipeHandle interface_pipe) {
    // TODO(crbug.com/977637): Get rid of the use of this implementation of
    // |service_manager::LocalInterfaceProvider|. This was done only to avoid
    // churning spellcheck code while eliminating the "chrome" and
    // "chrome_renderer" services. Spellcheck is (and should remain) the only
    // consumer of this implementation.
    content::RenderThread::Get()->BindHostReceiver(
        mojo::GenericPendingReceiver(interface_name, std::move(interface_pipe)));
  }
  
  void AlloyContentRendererClient::WillDestroyCurrentMessageLoop() {
    base::AutoLock lock_scope(single_process_cleanup_lock_);
    single_process_cleanup_complete_ = true;
  }
  
  void AlloyContentRendererClient::OnBrowserCreated(
      content::RenderView* render_view,
      base::Optional<bool> is_windowless) {
  #if defined(OS_MAC)
    const bool windowless = is_windowless.has_value() && *is_windowless;
  
    // FIXME: It would be better if this API would be a callback from the
    // WebKit layer, or if it would be exposed as an WebView instance method; the
    // current implementation uses a static variable, and WebKit needs to be
    // patched in order to make it work for each WebView instance
    render_view->GetWebView()->SetUseExternalPopupMenusThisInstance(!windowless);
  #endif
  }
  
  void AlloyContentRendererClient::RunSingleProcessCleanupOnUIThread() {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  
    // Clean up the single existing RenderProcessHost.
    content::RenderProcessHost* host = nullptr;
    content::RenderProcessHost::iterator iterator(
        content::RenderProcessHost::AllHostsIterator());
    if (!iterator.IsAtEnd()) {
      host = iterator.GetCurrentValue();
      host->Cleanup();
      iterator.Advance();
      DCHECK(iterator.IsAtEnd());
    }
    DCHECK(host);
  
    // Clear the run_renderer_in_process() flag to avoid a DCHECK in the
    // RenderProcessHost destructor.
    content::RenderProcessHost::SetRunRendererInProcess(false);
  
    // Deletion of the RenderProcessHost object will stop the render thread and
    // result in a call to WillDestroyCurrentMessageLoop.
    // Cleanup() will cause deletion to be posted as a task on the UI thread but
    // this task will only execute when running in multi-threaded message loop
    // mode (because otherwise the UI message loop has already stopped). Therefore
    // we need to explicitly delete the object when not running in this mode.
    if (!CefContext::Get()->settings().multi_threaded_message_loop)
      delete host;
  }
  
  // Enable deprecation warnings on Windows. See http://crbug.com/585142.
  #if defined(OS_WIN)
  #if defined(__clang__)
  #pragma GCC diagnostic pop
  #else
  #pragma warning(pop)
  #endif
  #endif
  