// Copyright 2015 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_MANAGER_H_
#define CEF_LIBCEF_BROWSER_BROWSER_INFO_MANAGER_H_
#pragma once

#include <list>
#include <map>
#include <memory>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "cef/include/cef_client.h"
#include "cef/libcef/browser/browser_info.h"
#include "cef/libcef/common/mojom/cef.mojom.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_process_host_observer.h"
#include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
#include "ui/base/window_open_disposition.h"
#include "url/gurl.h"

namespace blink {
struct WebWindowFeatures;
}

namespace content {
struct OpenURLParams;
struct Referrer;
class RenderFrameHost;
class RenderViewHostDelegateView;
class WebContents;
class WebContentsView;
}  // namespace content

class CefBrowserHostBase;
class CefBrowserPlatformDelegate;

// Singleton object for managing BrowserInfo instances.
class CefBrowserInfoManager : public content::RenderProcessHostObserver {
 public:
  CefBrowserInfoManager();

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

  ~CefBrowserInfoManager() override;

  // Returns this singleton instance of this class.
  static CefBrowserInfoManager* GetInstance();

  // Called immediately before a new CefBrowserHost implementation is created
  // directly. In this case |is_popup| will be true only for DevTools browsers.
  scoped_refptr<CefBrowserInfo> CreateBrowserInfo(
      bool is_popup,
      bool is_windowless,
      bool print_preview_enabled,
      CefRefPtr<CefDictionaryValue> extra_info);

  // Called from WebContentsDelegate::WebContentsCreated when a new browser is
  // being created for a traditional popup (e.g. window.open() or targeted
  // link). If any OnGetNewBrowserInfo requests are pending for the popup the
  // response will be sent when this method is called.
  scoped_refptr<CefBrowserInfo> CreatePopupBrowserInfo(
      content::WebContents* new_contents,
      bool is_windowless,
      bool print_preview_enabled,
      CefRefPtr<CefDictionaryValue> extra_info);

  // Called from ContentBrowserClient::CanCreateWindow. See comments on
  // PendingPopup for more information.
  bool CanCreateWindow(content::RenderFrameHost* opener,
                       const GURL& target_url,
                       const content::Referrer& referrer,
                       const std::string& frame_name,
                       WindowOpenDisposition disposition,
                       const blink::mojom::WindowFeatures& features,
                       bool user_gesture,
                       bool opener_suppressed,
                       bool* no_javascript_access);

  // Called from WebContentsDelegate::GetCustomWebContentsView (Alloy style
  // only). See comments on PendingPopup for more information.
  void GetCustomWebContentsView(
      const GURL& target_url,
      const content::GlobalRenderFrameHostId& opener_global_id,
      raw_ptr<content::WebContentsView>* view,
      raw_ptr<content::RenderViewHostDelegateView>* delegate_view);

  // Called from WebContentsDelegate::WebContentsCreated. See comments on
  // PendingPopup for more information.
  void WebContentsCreated(
      const GURL& target_url,
      const content::GlobalRenderFrameHostId& opener_global_id,
      CefBrowserSettings& settings,
      CefRefPtr<CefClient>& client,
      std::unique_ptr<CefBrowserPlatformDelegate>& platform_delegate,
      CefRefPtr<CefDictionaryValue>& extra_info,
      content::WebContents* new_contents);

  // Called from ChromeBrowserDelegate::AddWebContents. See comments on
  // PendingPopup for more information. Returns true for custom handling.
  bool AddWebContents(content::WebContents* source_contents);

  // Called from CefBrowserManager::GetNewBrowserInfo for delivering
  // browser info to the renderer process. If the browser info already exists
  // the response will be sent immediately. Otherwise, the response will be sent
  // when CreatePopupBrowserInfo creates the browser info. The info will already
  // exist for explicitly created browsers. It may sometimes already exist for
  // traditional popup browsers depending on timing. See comments on
  // PendingPopup for more information.
  void OnGetNewBrowserInfo(
      const content::GlobalRenderFrameHostToken& global_token,
      cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback);

  // Called from CefBrowserHostBase::DestroyBrowser() when a browser is
  // destroyed.
  void RemoveBrowserInfo(scoped_refptr<CefBrowserInfo> browser_info);

  // Called from CefContext::FinishShutdownOnUIThread() to destroy all browsers.
  void DestroyAllBrowsers();

  // Returns the CefBrowserInfo matching the specified ID/token or nullptr if no
  // match is found. It is allowed to add new callers of this method but
  // consider using CefBrowserHostBase::GetBrowserForGlobalId/Token() instead.
  scoped_refptr<CefBrowserInfo> GetBrowserInfo(
      const content::GlobalRenderFrameHostId& global_id);
  scoped_refptr<CefBrowserInfo> GetBrowserInfo(
      const content::GlobalRenderFrameHostToken& global_token);

  // Returns all existing CefBrowserInfo objects.
  using BrowserInfoList = std::list<scoped_refptr<CefBrowserInfo>>;
  BrowserInfoList GetBrowserInfoList();

  // Returns true if the navigation should be allowed to proceed, or false if
  // the navigation will instead be sent via OpenURLFromTab. If allowed,
  // |browser| will be set to the target browser if any.
  bool MaybeAllowNavigation(content::RenderFrameHost* opener,
                            const content::OpenURLParams& params,
                            CefRefPtr<CefBrowserHostBase>& browser) const;

  static bool ShouldCreateViewsHostedPopup(CefRefPtr<CefBrowserHostBase> opener,
                                           bool use_default_browser_creation);

  // Returns the FrameHost associated with |rfh|, if any. |browser_info| and
  // |is_excluded| will be populated if non-nullptr. An excluded type will not
  // have a FrameHost but |browser_info| may still be populated if the
  // association is known.
  static CefRefPtr<CefFrameHostImpl> GetFrameHost(content::RenderFrameHost* rfh,
                                                  bool prefer_speculative,
                                                  CefBrowserInfo** browser_info,
                                                  bool* is_excluded);

  // Returns true if |rfh| should be excluded (no FrameHost created).
  static bool IsExcludedFrameHost(content::RenderFrameHost* rfh);

 private:
  // RenderProcessHostObserver methods:
  void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;

  // Store state information about pending popups. Call order is:
  // - CanCreateWindow (UIT):
  //   Provides an opportunity to cancel the popup (calls OnBeforePopup) and
  //   creates the new platform delegate for the popup. If the popup owner is
  //   an extension guest view (PDF viewer) then the popup is canceled and
  //   WebContentsDelegate::OpenURLFromTab is called via the
  //   CefBrowserHostBase::MaybeAllowNavigation implementation.
  // And then the following calls may occur at the same time:
  // - GetCustomWebContentsView (UIT) (Alloy style only):
  //   Creates the OSR views for windowless popups.
  // - WebContentsCreated (UIT):
  //   Creates the CefBrowserHost representation for the popup.
  // - AddWebContents (UIT) (Chrome style only):
  //   Creates the Browser or tab representation for the popup.
  // - CefBrowserManager::GetNewBrowserInfo (IOT)
  //   Passes information about the popup to the renderer process.
  struct PendingPopup {
    // Track the last method that modified this PendingPopup instance. There may
    // be multiple pending popups with the same identifiers and this allows us
    // to differentiate between them at different processing steps.
    enum Step {
      CAN_CREATE_WINDOW,
      GET_CUSTOM_WEB_CONTENTS_VIEW,
      WEB_CONTENTS_CREATED,
    } step;

    // True if this popup is Alloy style, otherwise Chrome style.
    bool alloy_style;

    // Initial state from ViewHostMsg_CreateWindow.
    // |target_url| will be empty if a popup is created via window.open() and
    // never navigated. For example: javascript:window.open();
    content::GlobalRenderFrameHostId opener_global_id;
    GURL target_url;
    std::string target_frame_name;

    // Values specified by OnBeforePopup.
    CefBrowserSettings settings;
    CefRefPtr<CefClient> client;
    CefRefPtr<CefDictionaryValue> extra_info;

    // Platform delegate specific to the new popup.
    std::unique_ptr<CefBrowserPlatformDelegate> platform_delegate;

    // True if default Browser or tab creation should proceed from
    // AddWebContents (Chrome style only).
    bool use_default_browser_creation = false;

    // The newly created WebContents (set in WebContentsCreated).
    raw_ptr<content::WebContents> new_contents = nullptr;
  };

  // Manage pending popups. Only called on the UI thread.
  void PushPendingPopup(std::unique_ptr<PendingPopup> popup);

  // Used after CanCreateWindow is called.
  std::unique_ptr<PendingPopup> PopPendingPopup(
      PendingPopup::Step previous_step_alloy,
      PendingPopup::Step previous_step_chrome,
      const content::GlobalRenderFrameHostId& opener_global_id,
      const GURL& target_url);

  // Used after WebContentsCreated is called.
  std::unique_ptr<PendingPopup> PopPendingPopup(
      PendingPopup::Step previous_step_alloy,
      PendingPopup::Step previous_step_chrome,
      content::WebContents* new_contents);

  // Retrieves the BrowserInfo matching the specified ID/token.
  scoped_refptr<CefBrowserInfo> GetBrowserInfoInternal(
      const content::GlobalRenderFrameHostId& global_id);
  scoped_refptr<CefBrowserInfo> GetBrowserInfoInternal(
      const content::GlobalRenderFrameHostToken& global_token);

  // Check for excluded frames that can be responded to immediately.
  static void CheckExcludedNewBrowserInfoOnUIThread(
      const content::GlobalRenderFrameHostToken& global_token);

  void ContinueNewBrowserInfo(
      const content::GlobalRenderFrameHostToken& global_token,
      scoped_refptr<CefBrowserInfo> browser_info,
      bool is_excluded);

  // Send the response for a pending OnGetNewBrowserInfo request.
  static void SendNewBrowserInfoResponse(
      scoped_refptr<CefBrowserInfo> browser_info,
      bool is_excluded,
      cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback,
      scoped_refptr<base::SequencedTaskRunner> callback_runner);

  // Pending request for OnGetNewBrowserInfo.
  struct PendingNewBrowserInfo {
    content::GlobalRenderFrameHostToken global_token;
    int timeout_id;
    cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback;
    scoped_refptr<base::SequencedTaskRunner> callback_runner;
  };

  // Cancel a response that is still pending.
  static void CancelNewBrowserInfoResponse(PendingNewBrowserInfo* pending_info);

  // Time out a response if it's still pending.
  static void TimeoutNewBrowserInfoResponse(
      const content::GlobalRenderFrameHostToken& global_token,
      int timeout_id);

  mutable base::Lock browser_info_lock_;

  // Access to the below members must be protected by |browser_info_lock_|.

  BrowserInfoList browser_info_list_;
  int next_browser_id_ = 0;

  // Map of global ID to info. These IDs are guaranteed to uniquely
  // identify a RFH for its complete lifespan. See documentation on
  // RenderFrameHost::GetFrameTreeNodeId() for background.
  using PendingNewBrowserInfoMap =
      std::map<content::GlobalRenderFrameHostToken,
               std::unique_ptr<PendingNewBrowserInfo>>;
  PendingNewBrowserInfoMap pending_new_browser_info_map_;

  // Only accessed on the UI thread.
  using PendingPopupList = std::vector<std::unique_ptr<PendingPopup>>;
  PendingPopupList pending_popup_list_;

  int next_timeout_id_ = 0;
};

#endif  // CEF_LIBCEF_BROWSER_BROWSER_INFO_H_