// Copyright (c) 2012 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that can
// be found in the LICENSE file.

#ifndef CEF_LIBCEF_BROWSER_BROWSER_INFO_H_
#define CEF_LIBCEF_BROWSER_BROWSER_INFO_H_
#pragma once

#include <queue>
#include <set>
#include <unordered_map>

#include "base/containers/unique_ptr_adapters.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/synchronization/lock.h"
#include "base/values.h"
#include "cef/include/internal/cef_ptr.h"
#include "cef/libcef/common/values_impl.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_host.h"

class CefBrowserHostBase;
class CefFrameHandler;
class CefFrameHostImpl;

// CefBrowserInfo is used to associate a browser ID and render view/process
// IDs with a particular CefBrowserHostBase. Render view/process IDs may change
// during the lifetime of a single CefBrowserHostBase.
//
// CefBrowserInfo objects are managed by CefBrowserInfoManager and should not be
// created directly.
class CefBrowserInfo : public base::RefCountedThreadSafe<CefBrowserInfo> {
 public:
  CefBrowserInfo(int browser_id,
                 bool is_popup,
                 bool is_windowless,
                 bool print_preview_enabled,
                 CefRefPtr<CefDictionaryValue> extra_info);

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

  int browser_id() const { return browser_id_; }
  bool is_popup() const { return is_popup_; }
  bool is_windowless() const { return is_windowless_; }
  bool print_preview_enabled() const { return print_preview_enabled_; }
  CefRefPtr<CefDictionaryValue> extra_info() const { return extra_info_; }

  // May return nullptr if the browser has not yet been created (before
  // SetBrowser) or if the browser has been destroyed (after BrowserDestroyed).
  CefRefPtr<CefBrowserHostBase> browser() const;

  // Returns true if the browser has been created (after SetBrowser) and is not
  // yet closing (before SetClosing or WebContentsDestroyed).
  bool IsValid() const;

  // Returns true if the browser is closing (after SetClosing or
  // WebContentsDestroyed).
  bool IsClosing() const;

  // Called from CefBrowserHostBase constructor.
  void SetBrowser(CefRefPtr<CefBrowserHostBase> browser);

  // Called from CefBrowserHostBase::OnBeforeClose.
  void SetClosing();

  // Called from CefBrowserHostBase::DestroyWebContents.
  void WebContentsDestroyed();

  // Called from CefBrowserHostBase::DestroyBrowser.
  void BrowserDestroyed();

  // Ensure that a frame record exists for |host|. Called for the main frame
  // when the RenderView is created, or for a sub-frame when the associated
  // RenderFrame is created in the renderer process.
  // Called from CefBrowserContentsDelegate::RenderFrameCreated.
  void MaybeCreateFrame(content::RenderFrameHost* host);

  // Used to track state changes such as entering/exiting the BackForwardCache.
  // Called from CefBrowserContentsDelegate::RenderFrameHostStateChanged.
  void FrameHostStateChanged(
      content::RenderFrameHost* host,
      content::RenderFrameHost::LifecycleState old_state,
      content::RenderFrameHost::LifecycleState new_state);

  // Remove the frame record for |host|. Called for the main frame when the
  // RenderView is destroyed, or for a sub-frame when the associated RenderFrame
  // is destroyed in the renderer process.
  // Called from CefBrowserContentsDelegate::RenderFrameDeleted or
  // CefMimeHandlerViewGuestDelegate::OnGuestDetached.
  void RemoveFrame(content::RenderFrameHost* host);

  // Returns the main frame object. This object will remain valid until the
  // browser is destroyed even though the indentifier may change with cross-
  // origin navigations. Furthermore, calling LoadURL on this object will always
  // behave as expected because the call is routed through the browser's
  // NavigationController.
  CefRefPtr<CefFrameHostImpl> GetMainFrame();

  // Creates a temporary sub-frame object for situations during navigation or
  // resource loading where a RFH does not yet exist. If |parent_frame_id|
  // is invalid the current main frame will be specified as the parent.
  // Temporary frame objects are not tracked but will be implicitly detached
  // on browser destruction.
  CefRefPtr<CefFrameHostImpl> CreateTempSubFrame(
      const content::GlobalRenderFrameHostId& parent_global_id);

  // Returns the frame object matching the specified host or nullptr if no match
  // is found. Must be called on the UI thread.
  CefRefPtr<CefFrameHostImpl> GetFrameForHost(
      const content::RenderFrameHost* host,
      bool prefer_speculative = false) const;

  // Returns the frame object matching the specified ID/token or nullptr if no
  // match is found. Safe to call from any thread.
  CefRefPtr<CefFrameHostImpl> GetFrameForGlobalId(
      const content::GlobalRenderFrameHostId& global_id,
      bool prefer_speculative = false) const;
  CefRefPtr<CefFrameHostImpl> GetFrameForGlobalToken(
      const content::GlobalRenderFrameHostToken& global_token,
      bool prefer_speculative = false) const;

  // Returns all non-speculative frame objects that currently exist. Safe to
  // call from any thread.
  using FrameHostList = std::set<CefRefPtr<CefFrameHostImpl>>;
  FrameHostList GetAllFrames() const;

  class NavigationLock final : public base::RefCounted<NavigationLock> {
   private:
    friend class CefBrowserInfo;
    friend class base::RefCounted<NavigationLock>;

    // All usage is via friend declaration. NOLINTNEXTLINE
    NavigationLock();
    // All usage is via friend declaration. NOLINTNEXTLINE
    ~NavigationLock();

    base::OnceClosure pending_action_;
    base::WeakPtrFactory<NavigationLock> weak_ptr_factory_;
  };

  // Block navigation actions on NavigationLock life span. Must be called on the
  // UI thread.
  scoped_refptr<NavigationLock> CreateNavigationLock();

  // Returns true if navigation actions are currently blocked. If this method
  // returns true the most recent |pending_action| will be executed on the UI
  // thread once the navigation lock is released. Must be called on the UI
  // thread.
  bool IsNavigationLocked(base::OnceClosure pending_action);

  using FrameNotifyOnceAction =
      base::OnceCallback<void(CefRefPtr<CefFrameHandler>)>;

  // Specifies a CefFrameHandler notification action whose execution may need
  // to be blocked on release of a potentially held NotificationStateLock. If no
  // CefFrameHandler exists then the action will be discarded without executing.
  // If the NotificationStateLock is not currently held then the action will be
  // executed immediately.
  void MaybeExecuteFrameNotification(FrameNotifyOnceAction pending_action);

  void MaybeNotifyDraggableRegionsChanged(
      CefRefPtr<CefBrowserHostBase> browser,
      CefRefPtr<CefFrameHostImpl> frame,
      std::vector<CefDraggableRegion> draggable_regions);

 private:
  friend class base::RefCountedThreadSafe<CefBrowserInfo>;

  virtual ~CefBrowserInfo();

  struct FrameInfo {
    ~FrameInfo();

    inline bool IsCurrentMainFrame() const {
      return frame_ && is_main_frame_ && !is_speculative_ && !is_in_bfcache_;
    }

    content::GlobalRenderFrameHostId global_id_;
    bool is_main_frame_;
    bool is_speculative_;
    bool is_in_bfcache_ = false;
    CefRefPtr<CefFrameHostImpl> frame_;
  };

  void SetMainFrame(CefRefPtr<CefBrowserHostBase> browser,
                    CefRefPtr<CefFrameHostImpl> frame);

  void MaybeNotifyFrameCreated(CefRefPtr<CefFrameHostImpl> frame);
  void MaybeNotifyFrameDetached(CefRefPtr<CefBrowserHostBase> browser,
                                CefRefPtr<CefFrameHostImpl> frame);
  void MaybeNotifyMainFrameChanged(CefRefPtr<CefBrowserHostBase> browser,
                                   CefRefPtr<CefFrameHostImpl> old_frame,
                                   CefRefPtr<CefFrameHostImpl> new_frame);

  void RemoveAllFrames(CefRefPtr<CefBrowserHostBase> old_browser);

  const int browser_id_;
  const bool is_popup_;
  const bool is_windowless_;
  const bool print_preview_enabled_;
  CefRefPtr<CefDictionaryValue> extra_info_;

  // Navigation will be blocked while |navigation_lock_| exists.
  // Only accessed on the UI thread.
  base::WeakPtr<NavigationLock> navigation_lock_;

  // Used instead of |base::AutoLock(lock_)| in situations that might generate
  // CefFrameHandler notifications. Any notifications passed to
  // MaybeExecuteFrameNotification() will be queued until the lock is released,
  // and then executed in order. Only accessed on the UI thread.
  class NotificationStateLock final {
   public:
    explicit NotificationStateLock(CefBrowserInfo* browser_info);
    ~NotificationStateLock();

   protected:
    friend class CefBrowserInfo;
    const raw_ptr<CefBrowserInfo> browser_info_;
    CefRefPtr<CefFrameHandler> frame_handler_;
    std::unique_ptr<base::MovableAutoLock> browser_info_lock_scope_;
    std::queue<FrameNotifyOnceAction> queue_;
  };

  mutable base::Lock notification_lock_;

  // These members must be protected by |notification_lock_|.
  raw_ptr<NotificationStateLock> notification_state_lock_ = nullptr;
  CefRefPtr<CefFrameHandler> frame_handler_;

  mutable base::Lock lock_;

  // The below members must be protected by |lock_|.

  CefRefPtr<CefBrowserHostBase> browser_;

  // Owner of FrameInfo structs.
  using FrameInfoSet =
      std::set<std::unique_ptr<FrameInfo>, base::UniquePtrComparator>;
  FrameInfoSet frame_info_set_;

  // Map a global ID to one frame. These IDs are guaranteed to uniquely
  // identify a RFH for its complete lifespan. See documentation on
  // RenderFrameHost::GetFrameTreeNodeId() for background.
  using FrameIDMap = std::unordered_map<content::GlobalRenderFrameHostId,
                                        FrameInfo*,
                                        content::GlobalRenderFrameHostIdHasher>;
  FrameIDMap frame_id_map_;

  // Map of global token to global ID.
  using FrameTokenToIdMap = std::map<content::GlobalRenderFrameHostToken,
                                     content::GlobalRenderFrameHostId>;
  FrameTokenToIdMap frame_token_to_id_map_;

  // The current main frame.
  CefRefPtr<CefFrameHostImpl> main_frame_;

  // True if the browser is currently closing.
  bool is_closing_ = false;

  // Only accessed on the UI thread.
  std::vector<CefDraggableRegion> draggable_regions_;
};

#endif  // CEF_LIBCEF_BROWSER_BROWSER_INFO_H_