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.
  
  #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 <set>
  #include <vector>
  
  #include "base/macros.h"
  #include "base/memory/weak_ptr.h"
  #include "base/time/clock.h"
  #include "base/time/time.h"
  #include "base/timer/timer.h"
  #include "components/keyed_service/core/keyed_service.h"
  #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
  #include "components/no_state_prefetch/browser/no_state_prefetch_manager_delegate.h"
  #include "components/no_state_prefetch/browser/prerender_config.h"
  #include "components/no_state_prefetch/browser/prerender_histograms.h"
  #include "components/no_state_prefetch/common/no_state_prefetch_final_status.h"
  #include "components/no_state_prefetch/common/prerender_origin.h"
  #include "content/public/browser/render_process_host_observer.h"
  #include "third_party/abseil-cpp/absl/types/optional.h"
  #include "third_party/blink/public/mojom/prerender/prerender.mojom.h"
  #include "url/gurl.h"
  #include "url/origin.h"
  
  namespace base {
  class DictionaryValue;
  class ListValue;
  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 PrerenderHistory;
  
  // 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.
  
    // Adds a prerender for |url| if valid. |process_id| and |route_id| identify
    // the RenderView that the prerender request came from. If |size| is empty, a
    // default from the PrerenderConfig is used. Returns a NoStatePrefetchHandle
    // if the URL was added, NULL if it was not. If the launching RenderView is
    // itself prerendering, the prerender is added as a pending prerender.
    std::unique_ptr<NoStatePrefetchHandle> AddPrerenderFromLinkRelPrerender(
        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);
  
    // Adds a prerender for |url| if valid. As the prerender request is coming
    // from a source without a RenderFrameHost (i.e., the omnibox) we don't have a
    // child or route id, or a referrer. This method uses sensible values for
    // those. The |session_storage_namespace| matches the namespace of the active
    // tab at the time the prerender is generated from the omnibox. Returns a
    // NoStatePrefetchHandle or NULL. If the prerender fails, the prerender
    // manager may fallback and initiate a preconnect to |url|.
    std::unique_ptr<NoStatePrefetchHandle> AddPrerenderFromOmnibox(
        const GURL& url,
        content::SessionStorageNamespace* session_storage_namespace,
        const gfx::Size& size);
  
    // Adds a prerender for the prefetch url from NavigationPredictor on
    // page load, if NoStatePrefetch and prefetch_after_preconnect are true.
    // Uses the NavigationPredictor's browser context and the default
    // SessionStorageNamespace. Returns a NoStatePrefetchHandle or NULL.
    std::unique_ptr<NoStatePrefetchHandle> AddPrerenderFromNavigationPredictor(
        const GURL& url,
        content::SessionStorageNamespace* session_storage_namespace,
        const gfx::Size& size);
  
    // Adds a prerender for the prefetch url from IsolatedPrerender on
    // page load, if NoStatePrefetch and prefetch_after_preconnect are true.
    // Uses the NavigationPredictor's browser context and the default
    // SessionStorageNamespace. Returns a NoStatePrefetchHandle or nullptr. Does
    // not fallback to preconnecting if the prerender isn't triggered.
    std::unique_ptr<NoStatePrefetchHandle> AddIsolatedPrerender(
        const GURL& url,
        content::SessionStorageNamespace* session_storage_namespace,
        const gfx::Size& size);
  
    // Adds a NoStatePrefetch that only allows for same origin requests (i.e.,
    // requests that only redirect to the same origin).
    std::unique_ptr<NoStatePrefetchHandle> AddSameOriginSpeculation(
        const GURL& url,
        content::SessionStorageNamespace* session_storage_namespace,
        const gfx::Size& size,
        const url::Origin& initiator_origin);
  
    std::unique_ptr<NoStatePrefetchHandle> AddPrerenderFromExternalRequest(
        const GURL& url,
        const content::Referrer& referrer,
        content::SessionStorageNamespace* session_storage_namespace,
        const gfx::Rect& bounds);
  
    // Adds a prerender from an external request that will prerender even on
    // cellular networks as long as the user setting for prerendering is ON.
    std::unique_ptr<NoStatePrefetchHandle> AddForcedPrerenderFromExternalRequest(
        const GURL& url,
        const content::Referrer& referrer,
        content::SessionStorageNamespace* session_storage_namespace,
        const gfx::Rect& bounds);
  
    // 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 prerender pages to see if the given web contents
    // is prerendering a page.
    bool IsWebContentsPrerendering(
        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 prerendered.
    std::vector<content::WebContents*> GetAllPrerenderingContents() 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 Value object containing the active pages being prerendered, and
    // a history of pages which were prerendered.
    std::unique_ptr<base::DictionaryValue> CopyAsValue() 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 Config& config() const { return config_; }
    Config& 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 DisablePageLoadMetricsObserverForTesting() {
      page_load_metric_observer_disabled_ = true;
    }
  
    bool PageLoadMetricsObserverDisabledForTesting() const {
      return page_load_metric_observer_disabled_;
    }
  
    void AddObserver(std::unique_ptr<NoStatePrefetchManagerObserver> observer);
  
    // Notification that a prerender has completed and its bytes should be
    // recorded.
    void RecordNetworkBytesConsumed(Origin origin, int64_t prerender_bytes);
  
    // 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();
  
    // Returns true iff the |url| is found in the list of recent prefetches.
    bool HasRecentlyPrefetchedUrlForTesting(const GURL& url);
  
    // Adds a prerender for |url| from |initiator_origin|. The |origin| specifies
    // how the prerender was added. Returns a NoStatePrefetchHandle or nullptr.
    // Only for testing.
    std::unique_ptr<NoStatePrefetchHandle>
    AddPrerenderWithPreconnectFallbackForTesting(
        Origin origin,
        const GURL& url,
        const absl::optional<url::Origin>& initiator_origin);
  
   protected:
    class NoStatePrefetchData
        : public base::SupportsWeakPtr<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 abandon_time() const { return abandon_time_; }
  
      base::TimeTicks expiry_time() const { return expiry_time_; }
      void set_expiry_time(base::TimeTicks expiry_time) {
        expiry_time_ = expiry_time;
      }
  
     private:
      NoStatePrefetchManager* const 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;
  
      // The time when OnHandleNavigatedAway was called.
      base::TimeTicks abandon_time_;
  
      // After this time, this prefetch is no longer fresh, and should be removed.
      base::TimeTicks expiry_time_;
    };
  
    // 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;
  
    // Adds a prerender for |url| from |referrer|. The |origin| specifies how the
    // prerender was added. If |bounds| is empty, then
    // NoStatePrefetchContents::StartPrerendering will instead use a default from
    // PrerenderConfig. Returns a NoStatePrefetchHandle or NULL.
    std::unique_ptr<NoStatePrefetchHandle> AddPrerenderWithPreconnectFallback(
        Origin origin,
        const GURL& url,
        const content::Referrer& referrer,
        const absl::optional<url::Origin>& initiator_origin,
        const gfx::Rect& bounds,
        content::SessionStorageNamespace* session_storage_namespace);
  
    void StartSchedulingPeriodicCleanups();
    void StopSchedulingPeriodicCleanups();
  
    void EvictOldestPrerendersIfNecessary();
  
    // 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 absl::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 Value representing the pages currently being prerendered.
    std::unique_ptr<base::ListValue> GetActivePrerendersAsValue() const;
  
    // Records the final status a prerender in the case that a
    // NoStatePrefetchContents was never created, adds a PrerenderHistory 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.
    Config config_;
  
    // The browser_context that owns this NoStatePrefetchManager.
    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<PrerenderHistory> prerender_history_;
  
    const std::unique_ptr<PrerenderHistograms> histograms_;
  
    // The number of bytes transferred over the network for the browser_context
    // this NoStatePrefetchManager is attached to.
    int64_t browser_context_network_bytes_ = 0;
  
    // The value of browser_context_network_bytes_ that was last recorded.
    int64_t last_recorded_browser_context_network_bytes_ = 0;
  
    // Set of process hosts being prerendered.
    using PrerenderProcessSet = std::set<content::RenderProcessHost*>;
    PrerenderProcessSet prerender_process_hosts_;
  
    const base::TickClock* tick_clock_;
  
    bool page_load_metric_observer_disabled_ = false;
  
    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_
  