// 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/browser/browser_context.h"

#include <map>
#include <utility>

#include "libcef/browser/content_browser_client.h"
#include "libcef/browser/context.h"
#include "libcef/browser/download_manager_delegate.h"
#include "libcef/browser/extensions/extension_system.h"
#include "libcef/browser/prefs/browser_prefs.h"
#include "libcef/browser/request_context_impl.h"
#include "libcef/browser/ssl_host_state_delegate.h"
#include "libcef/browser/thread_util.h"
#include "libcef/common/cef_switches.h"
#include "libcef/common/extensions/extensions_util.h"

#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "chrome/browser/font_family_cache.h"
#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
#include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/guest_view/browser/guest_view_manager.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/core/simple_dependency_manager.h"
#include "components/keyed_service/core/simple_key_map.h"
#include "components/prefs/pref_service.h"
#include "components/proxy_config/pref_proxy_config_tracker_impl.h"
#include "components/user_prefs/user_prefs.h"
#include "components/visitedlink/browser/visitedlink_event_listener.h"
#include "components/visitedlink/browser/visitedlink_master.h"
#include "components/zoom/zoom_event_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/extension_protocols.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/constants.h"
#include "net/proxy_resolution/proxy_config_service.h"
#include "net/proxy_resolution/proxy_resolution_service.h"

using content::BrowserThread;

namespace {

// Manages the global list of Impl instances.
class ImplManager {
 public:
  typedef std::vector<CefBrowserContext*> Vector;

  ImplManager() {}
  ~ImplManager() {
    DCHECK(all_.empty());
    DCHECK(map_.empty());
  }

  void AddImpl(CefBrowserContext* impl) {
    CEF_REQUIRE_UIT();
    DCHECK(!IsValidImpl(impl));
    all_.push_back(impl);
  }

  void RemoveImpl(CefBrowserContext* impl, const base::FilePath& path) {
    CEF_REQUIRE_UIT();

    Vector::iterator it = GetImplPos(impl);
    DCHECK(it != all_.end());
    all_.erase(it);

    if (!path.empty()) {
      PathMap::iterator it = map_.find(path);
      DCHECK(it != map_.end());
      if (it != map_.end())
        map_.erase(it);
    }
  }

  bool IsValidImpl(const CefBrowserContext* impl) {
    CEF_REQUIRE_UIT();
    return GetImplPos(impl) != all_.end();
  }

  CefBrowserContext* GetImplForIDs(int render_process_id,
                                   int render_frame_id,
                                   int frame_tree_node_id,
                                   bool require_frame_match) {
    CEF_REQUIRE_UIT();
    for (const auto& context : all_) {
      if (context->IsAssociatedContext(render_process_id, render_frame_id,
                                       frame_tree_node_id,
                                       require_frame_match)) {
        return context;
      }
    }
    return nullptr;
  }

  CefBrowserContext* GetImplForContext(const content::BrowserContext* context) {
    CEF_REQUIRE_UIT();
    if (!context)
      return nullptr;

    Vector::iterator it = all_.begin();
    for (; it != all_.end(); ++it) {
      if (*it == context)
        return *it;
    }
    return nullptr;
  }

  void SetImplPath(CefBrowserContext* impl, const base::FilePath& path) {
    CEF_REQUIRE_UIT();
    DCHECK(!path.empty());
    DCHECK(IsValidImpl(impl));
    DCHECK(GetImplForPath(path) == nullptr);
    map_.insert(std::make_pair(path, impl));
  }

  CefBrowserContext* GetImplForPath(const base::FilePath& path) {
    CEF_REQUIRE_UIT();
    DCHECK(!path.empty());
    PathMap::const_iterator it = map_.find(path);
    if (it != map_.end())
      return it->second;
    return nullptr;
  }

  const Vector GetAllImpl() const { return all_; }

 private:
  Vector::iterator GetImplPos(const CefBrowserContext* impl) {
    Vector::iterator it = all_.begin();
    for (; it != all_.end(); ++it) {
      if (*it == impl)
        return it;
    }
    return all_.end();
  }

  typedef std::map<base::FilePath, CefBrowserContext*> PathMap;
  PathMap map_;

  Vector all_;

  DISALLOW_COPY_AND_ASSIGN(ImplManager);
};

#if DCHECK_IS_ON()
// Because of DCHECK()s in the object destructor.
base::LazyInstance<ImplManager>::DestructorAtExit g_manager =
    LAZY_INSTANCE_INITIALIZER;
#else
base::LazyInstance<ImplManager>::Leaky g_manager = LAZY_INSTANCE_INITIALIZER;
#endif

}  // namespace

// Creates and manages VisitedLinkEventListener objects for each
// CefBrowserContext sharing the same VisitedLinkMaster.
class CefVisitedLinkListener : public visitedlink::VisitedLinkMaster::Listener {
 public:
  CefVisitedLinkListener() { DCHECK(listener_map_.empty()); }

  void CreateListenerForContext(const CefBrowserContext* context) {
    CEF_REQUIRE_UIT();
    auto listener = std::make_unique<visitedlink::VisitedLinkEventListener>(
        const_cast<CefBrowserContext*>(context));
    listener_map_.insert(std::make_pair(context, std::move(listener)));
  }

  void RemoveListenerForContext(const CefBrowserContext* context) {
    CEF_REQUIRE_UIT();
    ListenerMap::iterator it = listener_map_.find(context);
    DCHECK(it != listener_map_.end());
    listener_map_.erase(it);
  }

  // visitedlink::VisitedLinkMaster::Listener methods.

  void NewTable(base::ReadOnlySharedMemoryRegion* table_region) override {
    CEF_REQUIRE_UIT();
    ListenerMap::iterator it = listener_map_.begin();
    for (; it != listener_map_.end(); ++it)
      it->second->NewTable(table_region);
  }

  void Add(visitedlink::VisitedLinkCommon::Fingerprint fingerprint) override {
    CEF_REQUIRE_UIT();
    ListenerMap::iterator it = listener_map_.begin();
    for (; it != listener_map_.end(); ++it)
      it->second->Add(fingerprint);
  }

  void Reset(bool invalidate_hashes) override {
    CEF_REQUIRE_UIT();
    ListenerMap::iterator it = listener_map_.begin();
    for (; it != listener_map_.end(); ++it)
      it->second->Reset(invalidate_hashes);
  }

 private:
  // Map of CefBrowserContext to the associated VisitedLinkEventListener.
  typedef std::map<const CefBrowserContext*,
                   std::unique_ptr<visitedlink::VisitedLinkEventListener>>
      ListenerMap;
  ListenerMap listener_map_;

  DISALLOW_COPY_AND_ASSIGN(CefVisitedLinkListener);
};

CefBrowserContext::CefBrowserContext(const CefRequestContextSettings& settings)
    : settings_(settings) {
  g_manager.Get().AddImpl(this);
}

CefBrowserContext::~CefBrowserContext() {
  CEF_REQUIRE_UIT();

  // No CefRequestContext should be referencing this object any longer.
  DCHECK(request_context_set_.empty());

  // Unregister the context first to avoid re-entrancy during shutdown.
  g_manager.Get().RemoveImpl(this, cache_path_);

  // Send notifications to clean up objects associated with this Profile.
  MaybeSendDestroyedNotification();

  ChromePluginServiceFilter::GetInstance()->UnregisterProfile(this);

  // Remove any BrowserContextKeyedServiceFactory associations. This must be
  // called before the ProxyService owned by CefBrowserContext is destroyed.
  // The SimpleDependencyManager should always be passed after the
  // BrowserContextDependencyManager. This is because the KeyedService instances
  // in the BrowserContextDependencyManager's dependency graph can depend on the
  // ones in the SimpleDependencyManager's graph.
  DependencyManager::PerformInterlockedTwoPhaseShutdown(
      BrowserContextDependencyManager::GetInstance(), this,
      SimpleDependencyManager::GetInstance(), key_.get());

  key_.reset();
  SimpleKeyMap::GetInstance()->Dissociate(this);

  // Shuts down the storage partitions associated with this browser context.
  // This must be called before the browser context is actually destroyed
  // and before a clean-up task for its corresponding IO thread residents
  // (e.g. ResourceContext) is posted, so that the classes that hung on
  // StoragePartition can have time to do necessary cleanups on IO thread.
  ShutdownStoragePartitions();

  if (resource_context_.get()) {
    // Destruction of the ResourceContext will trigger destruction of all
    // associated network requests.
    content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE,
                                       resource_context_.release());
  }

  visitedlink_listener_->RemoveListenerForContext(this);

  // The FontFamilyCache references the ProxyService so delete it before the
  // ProxyService is deleted.
  SetUserData(&kFontFamilyCacheKey, nullptr);

  pref_proxy_config_tracker_->DetachFromPrefService();

  if (host_content_settings_map_)
    host_content_settings_map_->ShutdownOnUIThread();

  // Delete the download manager delegate here because otherwise we'll crash
  // when it's accessed from the content::BrowserContext destructor.
  if (download_manager_delegate_)
    download_manager_delegate_.reset(nullptr);
}

void CefBrowserContext::Initialize() {
  CefContext* context = CefContext::Get();

  cache_path_ = base::FilePath(CefString(&settings_.cache_path));
  if (!context->ValidateCachePath(cache_path_)) {
    // Reset to in-memory storage.
    CefString(&settings_.cache_path).clear();
    cache_path_ = base::FilePath();
  }

  if (!cache_path_.empty())
    g_manager.Get().SetImplPath(this, cache_path_);

  if (settings_.accept_language_list.length == 0) {
    // Use the global language list setting.
    CefString(&settings_.accept_language_list) =
        CefString(&context->settings().accept_language_list);
  }

  if (!!settings_.persist_session_cookies) {
    set_should_persist_session_cookies(true);
  }

  key_ = std::make_unique<ProfileKey>(GetPath());
  SimpleKeyMap::GetInstance()->Associate(this, key_.get());

  // Initialize the PrefService object.
  pref_service_ = browser_prefs::CreatePrefService(
      this, cache_path_, !!settings_.persist_user_preferences);

  content::BrowserContext::Initialize(this, GetPath());

  resource_context_.reset(new CefResourceContext(IsOffTheRecord()));

  // This must be called before creating any services to avoid hitting
  // DependencyManager::AssertContextWasntDestroyed when creating/destroying
  // multiple browser contexts (due to pointer address reuse).
  BrowserContextDependencyManager::GetInstance()->CreateBrowserContextServices(
      this);

  const bool extensions_enabled = extensions::ExtensionsEnabled();
  if (extensions_enabled) {
    // Create the custom ExtensionSystem first because other KeyedServices
    // depend on it.
    extension_system_ = static_cast<extensions::CefExtensionSystem*>(
        extensions::ExtensionSystem::Get(this));
    extension_system_->InitForRegularProfile(true);

    // Make sure the ProcessManager is created so that it receives extension
    // load notifications. This is necessary for the proper initialization of
    // background/event pages.
    extensions::ProcessManager::Get(this);
  }

  // Initialize visited links management.
  base::FilePath visited_link_path;
  if (!cache_path_.empty())
    visited_link_path = cache_path_.Append(FILE_PATH_LITERAL("Visited Links"));
  visitedlink_listener_ = new CefVisitedLinkListener;
  visitedlink_master_.reset(new visitedlink::VisitedLinkMaster(
      visitedlink_listener_, this, !visited_link_path.empty(), false,
      visited_link_path, 0));
  visitedlink_listener_->CreateListenerForContext(this);
  visitedlink_master_->Init();

  // Initialize proxy configuration tracker.
  pref_proxy_config_tracker_.reset(new PrefProxyConfigTrackerImpl(
      GetPrefs(), base::CreateSingleThreadTaskRunner({BrowserThread::IO})));

  // Spell checking support and possibly other subsystems retrieve the
  // PrefService associated with a BrowserContext via UserPrefs::Get().
  PrefService* pref_service = GetPrefs();
  DCHECK(pref_service);
  user_prefs::UserPrefs::Set(this, pref_service);
  key_->SetPrefs(pref_service);

  if (extensions_enabled)
    extension_system_->Init();

  ChromePluginServiceFilter::GetInstance()->RegisterProfile(this);
}

void CefBrowserContext::AddCefRequestContext(CefRequestContextImpl* context) {
  CEF_REQUIRE_UIT();
  request_context_set_.insert(context);
}

void CefBrowserContext::RemoveCefRequestContext(
    CefRequestContextImpl* context) {
  CEF_REQUIRE_UIT();

  if (extensions::ExtensionsEnabled()) {
    extension_system()->OnRequestContextDeleted(context);
  }

  request_context_set_.erase(context);

  // Delete ourselves when the reference count reaches zero.
  if (request_context_set_.empty())
    delete this;
}

// static
CefBrowserContext* CefBrowserContext::GetForCachePath(
    const base::FilePath& cache_path) {
  return g_manager.Get().GetImplForPath(cache_path);
}

// static
CefBrowserContext* CefBrowserContext::GetForIDs(int render_process_id,
                                                int render_frame_id,
                                                int frame_tree_node_id,
                                                bool require_frame_match) {
  return g_manager.Get().GetImplForIDs(render_process_id, render_frame_id,
                                       frame_tree_node_id, require_frame_match);
}

// static
CefBrowserContext* CefBrowserContext::GetForContext(
    content::BrowserContext* context) {
  return g_manager.Get().GetImplForContext(context);
}

// static
std::vector<CefBrowserContext*> CefBrowserContext::GetAll() {
  return g_manager.Get().GetAllImpl();
}

content::ResourceContext* CefBrowserContext::GetResourceContext() {
  return resource_context_.get();
}

content::ClientHintsControllerDelegate*
CefBrowserContext::GetClientHintsControllerDelegate() {
  return nullptr;
}

void CefBrowserContext::SetCorsOriginAccessListForOrigin(
    const url::Origin& source_origin,
    std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
    std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
    base::OnceClosure closure) {
  // This method is called for Extension support.
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(closure));
}

ChromeZoomLevelPrefs* CefBrowserContext::GetZoomLevelPrefs() {
  return static_cast<ChromeZoomLevelPrefs*>(
      GetStoragePartition(this, nullptr)->GetZoomLevelDelegate());
}

scoped_refptr<network::SharedURLLoaderFactory>
CefBrowserContext::GetURLLoaderFactory() {
  return GetDefaultStoragePartition(this)
      ->GetURLLoaderFactoryForBrowserProcess();
}

base::FilePath CefBrowserContext::GetPath() {
  return cache_path_;
}

base::FilePath CefBrowserContext::GetPath() const {
  return cache_path_;
}

std::unique_ptr<content::ZoomLevelDelegate>
CefBrowserContext::CreateZoomLevelDelegate(
    const base::FilePath& partition_path) {
  if (cache_path_.empty())
    return std::unique_ptr<content::ZoomLevelDelegate>();

  return base::WrapUnique(new ChromeZoomLevelPrefs(
      GetPrefs(), cache_path_, partition_path,
      zoom::ZoomEventManager::GetForBrowserContext(this)->GetWeakPtr()));
}

bool CefBrowserContext::IsOffTheRecord() const {
  // CEF contexts are never flagged as off-the-record. It causes problems
  // for the extension system.
  return false;
}

content::DownloadManagerDelegate*
CefBrowserContext::GetDownloadManagerDelegate() {
  if (!download_manager_delegate_) {
    content::DownloadManager* manager =
        BrowserContext::GetDownloadManager(this);
    download_manager_delegate_.reset(new CefDownloadManagerDelegate(manager));
  }
  return download_manager_delegate_.get();
}

content::BrowserPluginGuestManager* CefBrowserContext::GetGuestManager() {
  DCHECK(extensions::ExtensionsEnabled());
  return guest_view::GuestViewManager::FromBrowserContext(this);
}

storage::SpecialStoragePolicy* CefBrowserContext::GetSpecialStoragePolicy() {
  return nullptr;
}

content::PushMessagingService* CefBrowserContext::GetPushMessagingService() {
  return nullptr;
}

content::SSLHostStateDelegate* CefBrowserContext::GetSSLHostStateDelegate() {
  if (!ssl_host_state_delegate_.get())
    ssl_host_state_delegate_.reset(new CefSSLHostStateDelegate());
  return ssl_host_state_delegate_.get();
}

content::PermissionControllerDelegate*
CefBrowserContext::GetPermissionControllerDelegate() {
  return nullptr;
}

content::BackgroundFetchDelegate*
CefBrowserContext::GetBackgroundFetchDelegate() {
  return nullptr;
}

content::BackgroundSyncController*
CefBrowserContext::GetBackgroundSyncController() {
  return nullptr;
}

content::BrowsingDataRemoverDelegate*
CefBrowserContext::GetBrowsingDataRemoverDelegate() {
  return nullptr;
}

PrefService* CefBrowserContext::GetPrefs() {
  return pref_service_.get();
}

const PrefService* CefBrowserContext::GetPrefs() const {
  return pref_service_.get();
}

ProfileKey* CefBrowserContext::GetProfileKey() const {
  DCHECK(key_);
  return key_.get();
}

policy::SchemaRegistryService*
CefBrowserContext::GetPolicySchemaRegistryService() {
  NOTREACHED();
  return nullptr;
}

policy::UserCloudPolicyManager* CefBrowserContext::GetUserCloudPolicyManager() {
  NOTREACHED();
  return nullptr;
}

policy::ProfilePolicyConnector* CefBrowserContext::GetProfilePolicyConnector() {
  NOTREACHED();
  return nullptr;
}

const policy::ProfilePolicyConnector*
CefBrowserContext::GetProfilePolicyConnector() const {
  NOTREACHED();
  return nullptr;
}

const CefRequestContextSettings& CefBrowserContext::GetSettings() const {
  return settings_;
}

HostContentSettingsMap* CefBrowserContext::GetHostContentSettingsMap() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (!host_content_settings_map_.get()) {
    // The |is_incognito_profile| and |is_guest_profile| arguments are
    // intentionally set to false as they otherwise limit the types of values
    // that can be stored in the settings map (for example, default values set
    // via DefaultProvider::SetWebsiteSetting).
    host_content_settings_map_ =
        new HostContentSettingsMap(GetPrefs(), false, false, false);

    // Change the default plugin policy.
    const base::CommandLine* command_line =
        base::CommandLine::ForCurrentProcess();
    const std::string& plugin_policy_str =
        command_line->GetSwitchValueASCII(switches::kPluginPolicy);
    if (!plugin_policy_str.empty()) {
      ContentSetting plugin_policy = CONTENT_SETTING_ALLOW;
      if (base::LowerCaseEqualsASCII(plugin_policy_str,
                                     switches::kPluginPolicy_Detect)) {
        plugin_policy = CONTENT_SETTING_DETECT_IMPORTANT_CONTENT;
      } else if (base::LowerCaseEqualsASCII(plugin_policy_str,
                                            switches::kPluginPolicy_Block)) {
        plugin_policy = CONTENT_SETTING_BLOCK;
      }
      host_content_settings_map_->SetDefaultContentSetting(
          CONTENT_SETTINGS_TYPE_PLUGINS, plugin_policy);
    }
  }
  return host_content_settings_map_.get();
}

void CefBrowserContext::AddVisitedURLs(const std::vector<GURL>& urls) {
  visitedlink_master_->AddURLs(urls);
}

void CefBrowserContext::RebuildTable(
    const scoped_refptr<URLEnumerator>& enumerator) {
  // Called when visited links will not or cannot be loaded from disk.
  enumerator->OnComplete(true);
}

void CefBrowserContext::OnRenderFrameCreated(
    CefRequestContextImpl* request_context,
    int render_process_id,
    int render_frame_id,
    int frame_tree_node_id,
    bool is_main_frame,
    bool is_guest_view) {
  CEF_REQUIRE_UIT();
  DCHECK_GE(render_process_id, 0);
  DCHECK_GE(render_frame_id, 0);
  DCHECK_GE(frame_tree_node_id, 0);

  render_id_set_.insert(std::make_pair(render_process_id, render_frame_id));
  node_id_set_.insert(frame_tree_node_id);

  CefRefPtr<CefRequestContextHandler> handler = request_context->GetHandler();
  if (handler) {
    handler_map_.AddHandler(render_process_id, render_frame_id,
                            frame_tree_node_id, handler);

    if (resource_context_) {
      // Using base::Unretained() is safe because both this callback and
      // possible deletion of |resource_context_| will execute on the IO thread,
      // and this callback will be executed first.
      CEF_POST_TASK(CEF_IOT,
                    base::Bind(&CefResourceContext::AddHandler,
                               base::Unretained(resource_context_.get()),
                               render_process_id, render_frame_id,
                               frame_tree_node_id, handler));
    }
  }
}

void CefBrowserContext::OnRenderFrameDeleted(
    CefRequestContextImpl* request_context,
    int render_process_id,
    int render_frame_id,
    int frame_tree_node_id,
    bool is_main_frame,
    bool is_guest_view) {
  CEF_REQUIRE_UIT();
  DCHECK_GE(render_process_id, 0);
  DCHECK_GE(render_frame_id, 0);
  DCHECK_GE(frame_tree_node_id, 0);

  auto it1 =
      render_id_set_.find(std::make_pair(render_process_id, render_frame_id));
  if (it1 != render_id_set_.end())
    render_id_set_.erase(it1);

  auto it2 = node_id_set_.find(frame_tree_node_id);
  if (it2 != node_id_set_.end())
    node_id_set_.erase(it2);

  CefRefPtr<CefRequestContextHandler> handler = request_context->GetHandler();
  if (handler) {
    handler_map_.RemoveHandler(render_process_id, render_frame_id,
                               frame_tree_node_id);

    if (resource_context_) {
      // Using base::Unretained() is safe because both this callback and
      // possible deletion of |resource_context_| will execute on the IO thread,
      // and this callback will be executed first.
      CEF_POST_TASK(
          CEF_IOT,
          base::Bind(&CefResourceContext::RemoveHandler,
                     base::Unretained(resource_context_.get()),
                     render_process_id, render_frame_id, frame_tree_node_id));
    }
  }

  if (is_main_frame) {
    ClearPluginLoadDecision(render_process_id);
  }
}

CefRefPtr<CefRequestContextHandler> CefBrowserContext::GetHandler(
    int render_process_id,
    int render_frame_id,
    int frame_tree_node_id,
    bool require_frame_match) const {
  CEF_REQUIRE_UIT();
  return handler_map_.GetHandler(render_process_id, render_frame_id,
                                 frame_tree_node_id, require_frame_match);
}

bool CefBrowserContext::IsAssociatedContext(int render_process_id,
                                            int render_frame_id,
                                            int frame_tree_node_id,
                                            bool require_frame_match) const {
  CEF_REQUIRE_UIT();

  if (render_process_id >= 0 && render_frame_id >= 0) {
    const auto it1 =
        render_id_set_.find(std::make_pair(render_process_id, render_frame_id));
    if (it1 != render_id_set_.end())
      return true;
  }

  if (frame_tree_node_id >= 0) {
    const auto it2 = node_id_set_.find(frame_tree_node_id);
    if (it2 != node_id_set_.end())
      return true;
  }

  if (render_process_id >= 0 && !require_frame_match) {
    // Choose an arbitrary handler for the same process.
    for (const auto& render_ids : render_id_set_) {
      if (render_ids.first == render_process_id)
        return true;
    }
  }

  return false;
}

void CefBrowserContext::AddPluginLoadDecision(
    int render_process_id,
    const base::FilePath& plugin_path,
    bool is_main_frame,
    const url::Origin& main_frame_origin,
    chrome::mojom::PluginStatus status) {
  CEF_REQUIRE_UIT();
  DCHECK_GE(render_process_id, 0);
  DCHECK(!plugin_path.empty());

  plugin_load_decision_map_.insert(std::make_pair(
      std::make_pair(std::make_pair(render_process_id, plugin_path),
                     std::make_pair(is_main_frame, main_frame_origin)),
      status));
}

bool CefBrowserContext::HasPluginLoadDecision(
    int render_process_id,
    const base::FilePath& plugin_path,
    bool is_main_frame,
    const url::Origin& main_frame_origin,
    chrome::mojom::PluginStatus* status) const {
  CEF_REQUIRE_UIT();
  DCHECK_GE(render_process_id, 0);
  DCHECK(!plugin_path.empty());

  PluginLoadDecisionMap::const_iterator it = plugin_load_decision_map_.find(
      std::make_pair(std::make_pair(render_process_id, plugin_path),
                     std::make_pair(is_main_frame, main_frame_origin)));
  if (it == plugin_load_decision_map_.end())
    return false;

  *status = it->second;
  return true;
}

void CefBrowserContext::ClearPluginLoadDecision(int render_process_id) {
  CEF_REQUIRE_UIT();

  if (render_process_id == -1) {
    plugin_load_decision_map_.clear();
  } else {
    PluginLoadDecisionMap::iterator it = plugin_load_decision_map_.begin();
    while (it != plugin_load_decision_map_.end()) {
      if (it->first.first.first == render_process_id)
        it = plugin_load_decision_map_.erase(it);
      else
        ++it;
    }
  }
}

void CefBrowserContext::RegisterSchemeHandlerFactory(
    const std::string& scheme_name,
    const std::string& domain_name,
    CefRefPtr<CefSchemeHandlerFactory> factory) {
  if (resource_context_) {
    // Using base::Unretained() is safe because both this callback and possible
    // deletion of |resource_context_| will execute on the IO thread, and this
    // callback will be executed first.
    CEF_POST_TASK(CEF_IOT,
                  base::Bind(&CefResourceContext::RegisterSchemeHandlerFactory,
                             base::Unretained(resource_context_.get()),
                             scheme_name, domain_name, factory));
  }
}

void CefBrowserContext::ClearSchemeHandlerFactories() {
  if (resource_context_) {
    // Using base::Unretained() is safe because both this callback and possible
    // deletion of |resource_context_| will execute on the IO thread, and this
    // callback will be executed first.
    CEF_POST_TASK(CEF_IOT,
                  base::Bind(&CefResourceContext::ClearSchemeHandlerFactories,
                             base::Unretained(resource_context_.get())));
  }
}

network::mojom::NetworkContext* CefBrowserContext::GetNetworkContext() {
  CEF_REQUIRE_UIT();
  return GetDefaultStoragePartition(this)->GetNetworkContext();
}

DownloadPrefs* CefBrowserContext::GetDownloadPrefs() {
  CEF_REQUIRE_UIT();
  if (!download_prefs_) {
    download_prefs_.reset(new DownloadPrefs(this));
  }
  return download_prefs_.get();
}

bool CefBrowserContext::IsPrintPreviewSupported() const {
  CEF_REQUIRE_UIT();
  if (!extensions::PrintPreviewEnabled())
    return false;

  return !GetPrefs()->GetBoolean(prefs::kPrintPreviewDisabled);
}