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 "libcef/browser/browser_context.h"
  
  #include <map>
  #include <utility>
  
  #include "libcef/browser/iothread_state.h"
  #include "libcef/browser/media_router/media_router_manager.h"
  #include "libcef/browser/request_context_impl.h"
  #include "libcef/browser/thread_util.h"
  #include "libcef/common/cef_switches.h"
  
  #include "base/files/file_util.h"
  #include "base/lazy_instance.h"
  #include "base/logging.h"
  #include "base/strings/string_util.h"
  #include "content/public/browser/browser_context.h"
  #include "content/public/browser/browser_task_traits.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/storage_partition.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* GetImplFromIDs(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* GetImplFromBrowserContext(
        const content::BrowserContext* context) {
      CEF_REQUIRE_UIT();
      if (!context)
        return nullptr;
  
      for (const auto& bc : all_) {
        if (bc->AsBrowserContext() == context)
          return bc;
      }
      return nullptr;
    }
  
    void SetImplPath(CefBrowserContext* impl, const base::FilePath& path) {
      CEF_REQUIRE_UIT();
      DCHECK(!path.empty());
      DCHECK(IsValidImpl(impl));
      DCHECK(GetImplFromPath(path) == nullptr);
      map_.insert(std::make_pair(path, impl));
    }
  
    CefBrowserContext* GetImplFromPath(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
  
  CefBrowserContext* GetSelf(base::WeakPtr<CefBrowserContext> self) {
    CEF_REQUIRE_UIT();
    return self.get();
  }
  
  }  // namespace
  
  CefBrowserContext::CefBrowserContext(const CefRequestContextSettings& settings)
      : settings_(settings), weak_ptr_factory_(this) {
    g_manager.Get().AddImpl(this);
    getter_ = base::BindRepeating(GetSelf, weak_ptr_factory_.GetWeakPtr());
  }
  
  CefBrowserContext::~CefBrowserContext() {
    CEF_REQUIRE_UIT();
  #if DCHECK_IS_ON()
    DCHECK(is_shutdown_);
  #endif
  
    if (iothread_state_) {
      // Destruction of the CefIOThreadState will trigger destruction of all
      // associated network requests.
      content::BrowserThread::DeleteSoon(content::BrowserThread::IO, FROM_HERE,
                                         iothread_state_.release());
    }
  }
  
  void CefBrowserContext::Initialize() {
    cache_path_ = base::FilePath(CefString(&settings_.cache_path));
  
    if (!cache_path_.empty())
      g_manager.Get().SetImplPath(this, cache_path_);
  
    iothread_state_ = std::make_unique<CefIOThreadState>();
  }
  
  void CefBrowserContext::Shutdown() {
    CEF_REQUIRE_UIT();
  
  #if DCHECK_IS_ON()
    is_shutdown_ = true;
  #endif
  
    // 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_);
  
    // Destroy objects that may hold references to the MediaRouter.
    media_router_manager_.reset();
  }
  
  void CefBrowserContext::AddCefRequestContext(CefRequestContextImpl* context) {
    CEF_REQUIRE_UIT();
    request_context_set_.insert(context);
  }
  
  void CefBrowserContext::RemoveCefRequestContext(
      CefRequestContextImpl* context) {
    CEF_REQUIRE_UIT();
  
    request_context_set_.erase(context);
  
    // Delete ourselves when the reference count reaches zero.
    if (request_context_set_.empty()) {
      Shutdown();
      delete this;
    }
  }
  
  // static
  CefBrowserContext* CefBrowserContext::FromCachePath(
      const base::FilePath& cache_path) {
    return g_manager.Get().GetImplFromPath(cache_path);
  }
  
  // static
  CefBrowserContext* CefBrowserContext::FromIDs(int render_process_id,
                                                int render_frame_id,
                                                int frame_tree_node_id,
                                                bool require_frame_match) {
    return g_manager.Get().GetImplFromIDs(render_process_id, render_frame_id,
                                          frame_tree_node_id,
                                          require_frame_match);
  }
  
  // static
  CefBrowserContext* CefBrowserContext::FromBrowserContext(
      const content::BrowserContext* context) {
    return g_manager.Get().GetImplFromBrowserContext(context);
  }
  
  // static
  std::vector<CefBrowserContext*> CefBrowserContext::GetAll() {
    return g_manager.Get().GetAllImpl();
  }
  
  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 (iothread_state_) {
        // Using base::Unretained() is safe because both this callback and
        // possible deletion of |iothread_state_| will execute on the IO thread,
        // and this callback will be executed first.
        CEF_POST_TASK(CEF_IOT, base::Bind(&CefIOThreadState::AddHandler,
                                          base::Unretained(iothread_state_.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 (iothread_state_) {
        // Using base::Unretained() is safe because both this callback and
        // possible deletion of |iothread_state_| will execute on the IO thread,
        // and this callback will be executed first.
        CEF_POST_TASK(CEF_IOT, base::Bind(&CefIOThreadState::RemoveHandler,
                                          base::Unretained(iothread_state_.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 CefString& scheme_name,
      const CefString& domain_name,
      CefRefPtr<CefSchemeHandlerFactory> factory) {
    if (iothread_state_) {
      // Using base::Unretained() is safe because both this callback and possible
      // deletion of |iothread_state_| will execute on the IO thread, and this
      // callback will be executed first.
      CEF_POST_TASK(CEF_IOT,
                    base::Bind(&CefIOThreadState::RegisterSchemeHandlerFactory,
                               base::Unretained(iothread_state_.get()),
                               scheme_name, domain_name, factory));
    }
  }
  
  void CefBrowserContext::ClearSchemeHandlerFactories() {
    if (iothread_state_) {
      // Using base::Unretained() is safe because both this callback and possible
      // deletion of |iothread_state_| will execute on the IO thread, and this
      // callback will be executed first.
      CEF_POST_TASK(CEF_IOT,
                    base::Bind(&CefIOThreadState::ClearSchemeHandlerFactories,
                               base::Unretained(iothread_state_.get())));
    }
  }
  
  void CefBrowserContext::LoadExtension(
      const CefString& root_directory,
      CefRefPtr<CefDictionaryValue> manifest,
      CefRefPtr<CefExtensionHandler> handler,
      CefRefPtr<CefRequestContext> loader_context) {
    NOTIMPLEMENTED();
    if (handler)
      handler->OnExtensionLoadFailed(ERR_ABORTED);
  }
  
  bool CefBrowserContext::GetExtensions(std::vector<CefString>& extension_ids) {
    NOTIMPLEMENTED();
    return false;
  }
  
  CefRefPtr<CefExtension> CefBrowserContext::GetExtension(
      const CefString& extension_id) {
    NOTIMPLEMENTED();
    return nullptr;
  }
  
  bool CefBrowserContext::UnloadExtension(const CefString& extension_id) {
    NOTIMPLEMENTED();
    return false;
  }
  
  bool CefBrowserContext::IsPrintPreviewSupported() const {
    return true;
  }
  
  network::mojom::NetworkContext* CefBrowserContext::GetNetworkContext() {
    CEF_REQUIRE_UIT();
    auto browser_context = AsBrowserContext();
    return browser_context->GetDefaultStoragePartition(browser_context)
        ->GetNetworkContext();
  }
  
  CefMediaRouterManager* CefBrowserContext::GetMediaRouterManager() {
    CEF_REQUIRE_UIT();
    if (!media_router_manager_) {
      media_router_manager_.reset(new CefMediaRouterManager(AsBrowserContext()));
    }
    return media_router_manager_.get();
  }
  