// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_NO_STATE_PREFETCH_BROWSER_NO_STATE_PREFETCH_MANAGER_H_
#define COMPONENTS_NO_STATE_PREFETCH_BROWSER_NO_STATE_PREFETCH_MANAGER_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <set>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_config.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_histograms.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_manager_delegate.h"
#include "components/no_state_prefetch/common/no_state_prefetch_final_status.h"
#include "components/no_state_prefetch/common/no_state_prefetch_origin.h"
#include "content/public/browser/preloading_data.h"
#include "content/public/browser/render_process_host_observer.h"
#include "third_party/blink/public/mojom/prerender/prerender.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace base {
class TickClock;
}  // namespace base

namespace chrome_browser_net {
enum class NetworkPredictionStatus;
}

namespace content {
class WebContents;
class BrowserContext;
}  // namespace content

namespace gfx {
class Rect;
class Size;
}  // namespace gfx

namespace prerender {

namespace test_utils {
class PrerenderInProcessBrowserTest;
}

class NoStatePrefetchHandle;
class NoStatePrefetchHistory;

// Observer interface for NoStatePrefetchManager events.
class NoStatePrefetchManagerObserver {
 public:
  virtual ~NoStatePrefetchManagerObserver();

  // Called from the UI thread.
  virtual void OnFirstContentfulPaint() = 0;
};

// NoStatePrefetchManager is responsible for initiating and keeping prerendered
// views of web pages. All methods must be called on the UI thread unless
// indicated otherwise.
class NoStatePrefetchManager : public content::RenderProcessHostObserver,
                               public KeyedService {
 public:
  // One or more of these flags must be passed to ClearData() to specify just
  // what data to clear.  See function declaration for more information.
  enum ClearFlags {
    CLEAR_PRERENDER_CONTENTS = 0x1 << 0,
    CLEAR_PRERENDER_HISTORY = 0x1 << 1,
    CLEAR_MAX = 0x1 << 2
  };

  // Owned by a BrowserContext object for the lifetime of the browser_context.
  NoStatePrefetchManager(
      content::BrowserContext* browser_context,
      std::unique_ptr<NoStatePrefetchManagerDelegate> delegate);

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

  ~NoStatePrefetchManager() override;

  // From KeyedService:
  void Shutdown() override;

  // Entry points for adding prerenders.

  // Starts a prefetch for |url| if valid. |process_id| and |route_id| identify
  // the RenderView that the prefetch request came from. If |size| is empty, a
  // default from the NoStatePrefetchConfig is used. Returns a
  // NoStatePrefetchHandle if the URL was added, NULL if it was not. If the
  // launching RenderView is itself prefetching, the prefetch is added as a
  // pending prefetch.
  std::unique_ptr<NoStatePrefetchHandle> StartPrefetchingFromLinkRelPrerender(
      int process_id,
      int route_id,
      const GURL& url,
      blink::mojom::PrerenderTriggerType trigger_type,
      const content::Referrer& referrer,
      const url::Origin& initiator_origin,
      const gfx::Size& size);

  // Cancels all active prerenders.
  void CancelAllPrerenders();

  // Destroys all pending prerenders using FinalStatus.  Also deletes them as
  // well as any swapped out WebContents queued for destruction.
  // Used both on destruction, and when clearing the browsing history.
  void DestroyAllContents(FinalStatus final_status);

  // Moves a NoStatePrefetchContents to the pending delete list from the list of
  // active prerenders when prerendering should be cancelled.
  virtual void MoveEntryToPendingDelete(NoStatePrefetchContents* entry,
                                        FinalStatus final_status);

  // Query the list of current prefetches to see if the given web contents is
  // prefetching a page.
  bool IsWebContentsPrefetching(const content::WebContents* web_contents) const;

  // Returns the NoStatePrefetchContents object for the given web_contents,
  // otherwise returns NULL. Note that the NoStatePrefetchContents may have been
  // Destroy()ed, but not yet deleted.
  NoStatePrefetchContents* GetNoStatePrefetchContents(
      const content::WebContents* web_contents) const;

  // Returns the NoStatePrefetchContents object for a given child_id, route_id
  // pair, otherwise returns NULL. Note that the NoStatePrefetchContents may
  // have been Destroy()ed, but not yet deleted.
  virtual NoStatePrefetchContents* GetNoStatePrefetchContentsForRoute(
      int child_id,
      int route_id) const;

  // Returns a list of all WebContents being NoStatePrefetched.
  std::vector<content::WebContents*>
  GetAllNoStatePrefetchingContentsForTesting() const;

  // Checks whether |url| has been recently navigated to.
  bool HasRecentlyBeenNavigatedTo(Origin origin, const GURL& url);

  // Returns a `base::DictValue` object containing the active pages being
  // prerendered, and a history of pages which were prerendered.
  base::DictValue CopyAsDict() const;

  // Clears the data indicated by which bits of clear_flags are set.
  //
  // If the CLEAR_PRERENDER_CONTENTS bit is set, all active prerenders are
  // cancelled and then deleted, and any WebContents queued for destruction are
  // destroyed as well.
  //
  // If the CLEAR_PRERENDER_HISTORY bit is set, the prerender history is
  // cleared, including any entries newly created by destroying them in
  // response to the CLEAR_PRERENDER_CONTENTS flag.
  //
  // Intended to be used when clearing the cache or history.
  void ClearData(int clear_flags);

  // Record a final status of a prerendered page in a histogram.
  void RecordFinalStatus(Origin origin, FinalStatus final_status) const;

  const NoStatePrefetchConfig& config() const { return config_; }
  NoStatePrefetchConfig& mutable_config() { return config_; }

  // Records that some visible tab navigated (or was redirected) to the
  // provided URL.
  void RecordNavigation(const GURL& url);

  // Return current time and ticks with ability to mock the clock out for
  // testing.
  base::Time GetCurrentTime() const;
  base::TimeTicks GetCurrentTimeTicks() const;
  void SetTickClockForTesting(const base::TickClock* tick_clock);

  void AddObserver(std::unique_ptr<NoStatePrefetchManagerObserver> observer);

  // Registers a new ProcessHost performing a prerender. Called by
  // NoStatePrefetchContents.
  void AddPrerenderProcessHost(content::RenderProcessHost* process_host);

  // Returns whether or not |process_host| may be reused for new navigations
  // from a prerendering perspective. Currently, if Prerender Cookie Stores are
  // enabled, prerenders must be in their own processes that may not be shared.
  bool MayReuseProcessHost(content::RenderProcessHost* process_host);

  // content::RenderProcessHostObserver implementation.
  void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;

  // Cleans up the expired prefetches and then returns true if |url| was
  // no-state prefetched recently. If so, |prefetch_age|, |final_status| and
  // |origin| are set based on the no-state prefetch information if they are
  // non-null.
  bool GetPrefetchInformation(const GURL& url,
                              base::TimeDelta* prefetch_age,
                              FinalStatus* final_status,
                              Origin* origin);

  void SetNoStatePrefetchContentsFactoryForTest(
      NoStatePrefetchContents::Factory* no_state_prefetch_contents_factory);

  base::WeakPtr<NoStatePrefetchManager> AsWeakPtr();

  // Clears the list of recently prefetched URLs. Allows, for example, to reuse
  // the same URL in tests, without running into FINAL_STATUS_DUPLICATE.
  void ClearPrefetchInformationForTesting();

  // Starts a prefetch for |url| from |initiator_origin|. The |origin| specifies
  // how the prefetch was started. Returns a NoStatePrefetchHandle or nullptr.
  // Only for testing.
  std::unique_ptr<NoStatePrefetchHandle>
  StartPrefetchingWithPreconnectFallbackForTesting(
      Origin origin,
      const GURL& url,
      const std::optional<url::Origin>& initiator_origin);

 protected:
  class NoStatePrefetchData {
   public:
    struct OrderByExpiryTime;

    NoStatePrefetchData(NoStatePrefetchManager* manager,
                        std::unique_ptr<NoStatePrefetchContents> contents,
                        base::TimeTicks expiry_time);

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

    ~NoStatePrefetchData();

    // A new NoStatePrefetchHandle has been created for this
    // NoStatePrefetchData.
    void OnHandleCreated(NoStatePrefetchHandle* handle);

    // The launcher associated with a handle is navigating away from the context
    // that launched this prefetch. If the prerender is active, it may stay
    // alive briefly though, in case we we going through a redirect chain that
    // will eventually land at it.
    void OnHandleNavigatedAway(NoStatePrefetchHandle* handle);

    // The launcher associated with a handle has taken explicit action to cancel
    // this prefetch. We may well destroy the prerender in this case if no other
    // handles continue to track it.
    void OnHandleCanceled(NoStatePrefetchHandle* handle);

    NoStatePrefetchContents* contents() { return contents_.get(); }

    std::unique_ptr<NoStatePrefetchContents> ReleaseContents();

    int handle_count() const { return handle_count_; }

    base::TimeTicks expiry_time() const { return expiry_time_; }
    void set_expiry_time(base::TimeTicks expiry_time) {
      expiry_time_ = expiry_time;
    }

    base::WeakPtr<NoStatePrefetchData> AsWeakPtr() {
      return weak_factory_.GetWeakPtr();
    }

   private:
    const raw_ptr<NoStatePrefetchManager> manager_;
    std::unique_ptr<NoStatePrefetchContents> contents_;

    // The number of distinct NoStatePrefetchHandles created for |this|,
    // including ones that have called
    // NoStatePrefetchData::OnHandleNavigatedAway(), but not counting the ones
    // that have called NoStatePrefetchData::OnHandleCanceled(). For pending
    // prefetches, this will always be 1, since the NoStatePrefetchManager only
    // merges handles of running prefetches.
    int handle_count_ = 0;

    // After this time, this prefetch is no longer fresh, and should be removed.
    base::TimeTicks expiry_time_;

    base::WeakPtrFactory<NoStatePrefetchData> weak_factory_{this};
  };

  // Called by a NoStatePrefetchData to signal that the launcher has navigated
  // away from the context that launched the prefetch. A user may have clicked
  // a link in a page containing a <link rel=prerender> element, or the user
  // might have committed an omnibox navigation. This is used to possibly
  // shorten the TTL of the page for NoStatePrefetch.
  void SourceNavigatedAway(NoStatePrefetchData* prefetch_data);

  // Same as base::SysInfo::IsLowEndDevice(), overridden in tests.
  virtual bool IsLowEndDevice() const;

  // Whether network prediction is enabled for prefetch origin, |origin|.
  bool IsPredictionEnabled(Origin origin);

 private:
  friend class test_utils::PrerenderInProcessBrowserTest;
  friend class NoStatePrefetchContents;
  friend class NoStatePrefetchHandle;
  friend class UnitTestNoStatePrefetchManager;

  class OnCloseWebContentsDeleter;
  struct NavigationRecord;
  using NoStatePrefetchDataVector =
      std::vector<std::unique_ptr<NoStatePrefetchData>>;

  // Time interval before a new prefetch is allowed.
  static const int kMinTimeBetweenPrefetchesMs = 500;

  // Time window for which we record old navigations, in milliseconds.
  static const int kNavigationRecordWindowMs = 5000;

  // Starts a prefetch for |url| from |referrer|. The |origin| specifies how the
  // prefetch was started. If |bounds| is empty, then
  // NoStatePrefetchContents::StartPrerendering will instead use a default from
  // NoStatePrefetchConfig. Returns a NoStatePrefetchHandle or NULL.
  // PreloadingAttempt helps us to log various metrics associated with
  // particular NoStatePrefetch attempt.
  // TODO(crbug.com/40238653): Remove nullptr as default parameter once NSP is
  // integrated with all different predictors.
  std::unique_ptr<NoStatePrefetchHandle> StartPrefetchingWithPreconnectFallback(
      Origin origin,
      const GURL& url,
      const content::Referrer& referrer,
      const std::optional<url::Origin>& initiator_origin,
      const gfx::Rect& bounds,
      content::SessionStorageNamespace* session_storage_namespace,
      base::WeakPtr<content::PreloadingAttempt> attempt = nullptr);

  void StartSchedulingPeriodicCleanups();
  void StopSchedulingPeriodicCleanups();

  // Deletes stale and cancelled prerendered NoStatePrefetchContents, as well as
  // WebContents that have been replaced by prerendered WebContents.
  // Also identifies and kills NoStatePrefetchContents that use too much
  // resources.
  void PeriodicCleanup();

  // Posts a task to call PeriodicCleanup.  Results in quicker destruction of
  // objects.  If |this| is deleted before the task is run, the task will
  // automatically be cancelled.
  void PostCleanupTask();

  base::TimeTicks GetExpiryTimeForNewPrerender(Origin origin) const;
  base::TimeTicks GetExpiryTimeForNavigatedAwayPrerender() const;

  void DeleteOldEntries();

  void DeleteToDeletePrerenders();

  // Virtual so unit tests can override this.
  virtual std::unique_ptr<NoStatePrefetchContents>
  CreateNoStatePrefetchContents(
      const GURL& url,
      const content::Referrer& referrer,
      const std::optional<url::Origin>& initiator_origin,
      Origin origin);

  // Insures the |active_prefetches_| are sorted by increasing expiry time. Call
  // after every mutation of |active_prefetches_| that can possibly make it
  // unsorted (e.g. an insert, or changing an expiry time).
  void SortActivePrefetches();

  // Finds the active NoStatePrefetchData object for a running prefetch matching
  // |url| and |session_storage_namespace|.
  NoStatePrefetchData* FindNoStatePrefetchData(
      const GURL& url,
      content::SessionStorageNamespace* session_storage_namespace);

  // Given the |no_state_prefetch_contents|, find the iterator in
  // |active_prefetches_| corresponding to the given prefetch.
  NoStatePrefetchDataVector::iterator FindIteratorForNoStatePrefetchContents(
      NoStatePrefetchContents* no_state_prefetch_contents);

  bool DoesRateLimitAllowPrefetch(Origin origin) const;

  // Deletes old WebContents that have been replaced by prerendered ones.  This
  // is needed because they're replaced in a callback from the old WebContents,
  // so cannot immediately be deleted.
  void DeleteOldWebContents();

  // Called when NoStatePrefetchContents gets destroyed. Attaches the
  // |final_status| to the most recent prefetch matching the |url|.
  void SetPrefetchFinalStatusForUrl(const GURL& url, FinalStatus final_status);

  // Called when a prefetch has been used. Prefetches avoid cache revalidation
  // only once.
  void OnPrefetchUsed(const GURL& url);

  // Cleans up old NavigationRecord's.
  void CleanUpOldNavigations(std::vector<NavigationRecord>* navigations,
                             base::TimeDelta max_age);

  // Arrange for the given WebContents to be deleted asap. Delete |deleter| as
  // well.
  void ScheduleDeleteOldWebContents(std::unique_ptr<content::WebContents> tab,
                                    OnCloseWebContentsDeleter* deleter);

  // Adds to the history list.
  void AddToHistory(NoStatePrefetchContents* contents);

  // Returns a new `base::ListValue` representing the pages currently being
  // prerendered.
  base::ListValue GetActivePrerenders() const;

  // Records the final status a prerender in the case that a
  // NoStatePrefetchContents was never created, adds a NoStatePrefetchHistory
  // entry, and may also initiate a preconnect to |url|.
  void SkipNoStatePrefetchContentsAndMaybePreconnect(
      const GURL& url,
      Origin origin,
      FinalStatus final_status) const;

  // May initiate a preconnect to |url_arg| based on |origin|.
  void MaybePreconnect(Origin origin, const GURL& url_arg) const;

  // The configuration.
  NoStatePrefetchConfig config_;

  // The browser_context that owns this NoStatePrefetchManager.
  raw_ptr<content::BrowserContext> browser_context_;

  // The delegate that allows content embedder to override the logic in this
  // class.
  std::unique_ptr<NoStatePrefetchManagerDelegate> delegate_;

  // All running prefetches. Sorted by expiry time, in ascending order.
  NoStatePrefetchDataVector active_prefetches_;

  // Prefetches awaiting deletion.
  NoStatePrefetchDataVector to_delete_prefetches_;

  // List of recent navigations in this browser_context, sorted by ascending
  // |navigate_time_|.
  std::vector<NavigationRecord> navigations_;

  // List of recent prefetches, sorted by ascending navigate time.
  std::vector<NavigationRecord> prefetches_;

  std::unique_ptr<NoStatePrefetchContents::Factory>
      no_state_prefetch_contents_factory_;

  // RepeatingTimer to perform periodic cleanups of pending prerendered
  // pages.
  base::RepeatingTimer repeating_timer_;

  // Track time of last prefetch to limit prefetch spam.
  base::TimeTicks last_prefetch_start_time_;

  std::vector<std::unique_ptr<content::WebContents>> old_web_contents_list_;

  std::vector<std::unique_ptr<OnCloseWebContentsDeleter>>
      on_close_web_contents_deleters_;

  const std::unique_ptr<NoStatePrefetchHistory> prefetch_history_;

  const std::unique_ptr<NoStatePrefetchHistograms> histograms_;

  // Set of process hosts being prerendered.
  using PrerenderProcessSet =
      std::set<raw_ptr<content::RenderProcessHost, SetExperimental>>;
  PrerenderProcessSet prerender_process_hosts_;

  raw_ptr<const base::TickClock> tick_clock_;

  std::vector<std::unique_ptr<NoStatePrefetchManagerObserver>> observers_;

  base::WeakPtrFactory<NoStatePrefetchManager> weak_factory_{this};
};

}  // namespace prerender

#endif  // COMPONENTS_NO_STATE_PREFETCH_BROWSER_NO_STATE_PREFETCH_MANAGER_H_
