910e62b5创建于 1月15日历史提交
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_HOST_REGISTRY_H_
#define CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_HOST_REGISTRY_H_

#include <memory>
#include <set>
#include <string>
#include <vector>

#include "base/containers/circular_deque.h"
#include "base/containers/flat_map.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/timer/timer.h"
#include "base/types/pass_key.h"
#include "content/browser/preloading/preloading_confidence.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/reserved_prerender_host_info.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_export.h"
#include "content/common/frame.mojom-forward.h"
#include "content/public/browser/preloading.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom-shared.h"

class GURL;

namespace base {
class SingleThreadTaskRunner;
}

namespace net {
class HttpResponseHeaders;
}

namespace network {
class SimpleURLLoader;
}  // namespace network

namespace content {

class FrameTree;
class NavigationRequest;
class PrerenderCancellationReason;
class PrerenderHost;
class PrerenderNewTabHandle;
class RenderFrameHostImpl;
class StoredPage;
struct PrerenderAttributes;

// PrerenderHostRegistry creates and retains a prerender host, and reserves it
// for NavigationRequest to activate the prerendered page. This is created per
// WebContentsImpl and owned by it.
//
// The APIs of this class are categorized into two: APIs for triggers and APIs
// for activators.
//
// - Triggers (e.g., SpeculationHostImpl) start prerendering by
//   CreateAndStartHost() and notify the registry of destruction of the trigger
//   by CancelHosts().
// - Activators (i.e., NavigationRequest) can reserve the prerender host on
//   activation start by ReserveHostToActivate(), activate it by
//   ActivateReservedHost(), and notify the registry of completion of the
//   activation by OnActivationFinished().
class CONTENT_EXPORT PrerenderHostRegistry
    : public WebContentsObserver,
      public base::MemoryPressureListener {
 public:
  // The time to allow prerendering kept alive in the background. All the hosts
  // that this PrerenderHostRegistry holds will be terminated when the timer
  // exceeds this. The timeout value differs depending on the trigger type. The
  // value for an embedder was determined by
  // PageLoad.Clients.Prerender.NavigationToActivation.*.
  // The value for speculation rules was determined to align with the default
  // value of BFCache's eviction timer
  // (kDefaultTimeToLiveInBackForwardCacheInSeconds).
  static constexpr base::TimeDelta kTimeToLiveInBackgroundForEmbedder =
      base::Seconds(19);
  static constexpr base::TimeDelta kTimeToLiveInBackgroundForSpeculationRules =
      base::Seconds(600);
  static constexpr int kMaxRunningSpeculationRulesImmediatePrerenders = 10;
  static constexpr int kMaxRunningSpeculationRulesNonImmediatePrerenders = 2;

  using PassKey = base::PassKey<PrerenderHostRegistry>;

  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  //
  // LINT.IfChange(PrerenderProcessReuseAvailability)
  enum class PrerenderProcessReuseAvailability {
    kHasMatchableHosts = 0,
    kHasSameOriginHosts = 1,
    kHasSameSiteHosts = 2,
    kNoSameOriginOrSiteHosts = 3,
    kMaxValue = kNoSameOriginOrSiteHosts,
  };
  // LINT.ThenChange(//tools/metrics/histograms/metadata/navigation/enums.xml:PrerenderProcessReuseAvailability)

  explicit PrerenderHostRegistry(WebContents&);
  ~PrerenderHostRegistry() override;

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

  class Observer : public base::CheckedObserver {
   public:
    // Called once per CreateAndStartHost() call. Indicates the registry
    // received a request to create a prerender but does not necessarily mean a
    // host was created. If a host was created, it is guaranteed to be in the
    // registry at the time this is called.
    virtual void OnTrigger(const GURL& url) {}

    // Called when CancelHosts() actually cancels each host.
    virtual void OnCancel(FrameTreeNodeId host_frame_tree_node_id,
                          const PrerenderCancellationReason& reason) {}

    // Called from the registry's destructor. The observer
    // should drop any reference to the registry.
    virtual void OnRegistryDestroyed() {}
  };

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // For triggers.
  // Creates and starts a host. Returns the root frame tree node id of the
  // prerendered page, which can be used as the id of the host.
  // `preloading_attempt` is the attempt corresponding to this prerender, the
  // default value is set to nullptr as every case of prerendering trigger is
  // not yet integrated with PreloadingAttempt.
  // TODO(crbug.com/40234240): Remove the default value as nullptr for
  // preloading_attempt once new-tab-prerender is integrated with Preloading
  // APIs.
  FrameTreeNodeId CreateAndStartHost(
      const PrerenderAttributes& attributes,
      PreloadingAttempt* preloading_attempt = nullptr);

  // Creates and starts a host in a new WebContents so that a navigation in a
  // new tab will be able to activate it. PrerenderHostRegistry associated with
  // the new WebContents manages the started host, and `this`
  // PrerenderHostRegistry manages PrerenderNewTabHandle that owns the
  // WebContents (see `prerender_new_tab_handle_by_frame_tree_node_id_`).
  FrameTreeNodeId CreateAndStartHostForNewTab(
      const PrerenderAttributes& attributes,
      const PreloadingPredictor& creating_predictor,
      const PreloadingPredictor& enacting_predictor,
      PreloadingConfidence confidence);

  // Cancels the host registered for `frame_tree_node_id`. The host is
  // immediately removed from the map of non-reserved hosts but asynchronously
  // destroyed so that prerendered pages can cancel themselves without concern
  // for self destruction.
  // Returns true if a cancelation has occurred.
  bool CancelHost(FrameTreeNodeId frame_tree_node_id,
                  PrerenderFinalStatus final_status);
  // Same as CancelHost, but can pass a detailed reason for recording if given.
  bool CancelHost(FrameTreeNodeId frame_tree_node_id,
                  const PrerenderCancellationReason& reason);

  // Cancels the existing hosts specified in the vector with the same reason.
  // Returns a subset of `frame_tree_node_ids` that were actually cancelled.
  std::set<FrameTreeNodeId> CancelHosts(
      const std::vector<FrameTreeNodeId>& frame_tree_node_ids,
      const PrerenderCancellationReason& reason);

  // Applies CancelHost for all existing PrerenderHost.
  void CancelAllHosts(PrerenderFinalStatus final_status);

  // For activators. Finds the host to activate for a navigation for the given
  // NavigationRequest. Returns the root frame tree node id of the prerendered
  // page, which can be used as the id of the host. This doesn't reserve the
  // host so it can be destroyed or activated by another navigation. This also
  // cancels all the prerender hosts except the one to be activated. See also
  // comments on ReserveHostToActivate().
  FrameTreeNodeId FindPotentialHostToActivate(
      NavigationRequest& navigation_request);

  // For activators. Reserves the host to activate for a navigation for the
  // given NavigationRequest.
  // Returns a valid ReservedPrerenderHostInfo, which has the valid root frame
  // tree node if of the prerendered page. Returns nullopt if it's not found
  // or not ready for activation yet.
  // The caller is responsible for calling OnActivationFinished() with the id to
  // release the reserved host. This also cancels all the prerender hosts except
  // the one to be activated.
  std::optional<ReservedPrerenderHostInfo> ReserveHostToActivate(
      NavigationRequest& navigation_request,
      FrameTreeNodeId expected_host_id);

  // For activators.
  // Activates the host reserved by ReserveHostToActivate() and returns the
  // StoredPage containing the page that was activated on success, or nullptr
  // on failure.
  std::unique_ptr<StoredPage> ActivateReservedHost(
      FrameTreeNodeId frame_tree_node_id,
      NavigationRequest& navigation_request);

  RenderFrameHostImpl* GetRenderFrameHostForReservedHost(
      FrameTreeNodeId frame_tree_node_id);

  // For activators.
  // Called from the destructor of NavigationRequest that reserved the host.
  // `frame_tree_node_id` should be the id returned by ReserveHostToActivate().
  void OnActivationFinished(FrameTreeNodeId frame_tree_node_id);

  // Returns the non-reserved host with the given id. Returns nullptr if the id
  // does not match any non-reserved host.
  PrerenderHost* FindNonReservedHostById(FrameTreeNodeId frame_tree_node_id);

  // Returns true if this registry reserves a host for activation.
  bool HasReservedHost() const;

  // Returns the ownership of a pre-created WebContentsImpl that contains a
  // prerendered page that corresponds to the given params for a new tab
  // navigation, if it exists.
  std::unique_ptr<WebContentsImpl> TakePreCreatedWebContentsForNewTabIfExists(
      const mojom::CreateNewWindowParams& create_new_window_params,
      const WebContents::CreateParams& web_contents_create_params);

  // Returns the FrameTrees owned by this registry's prerender hosts.
  std::vector<FrameTree*> GetPrerenderFrameTrees();

  // Returns the non-reserved host for `prerendering_url`. Returns nullptr if
  // the URL doesn't match any non-reserved host.
  PrerenderHost* FindHostByUrlForTesting(const GURL& prerendering_url);

  // Returns the prewarmed default search engine page. The prewarm page is
  // filtered by exactly matching the search prewarm URL with the initial URL of
  // the prerender host.
  PrerenderHost* FindPrewarmSearchResultHostForTesting(
      const GURL& search_prewarm_url);

  // Returns whether prerender_new_tab_handle_by_frame_tree_node_id_ has the
  // given id.
  bool HasNewTabHandleByIdForTesting(FrameTreeNodeId frame_tree_node_id);

  // Cancels all hosts.
  void CancelAllHostsForTesting();

  // Represents the group of prerender limit calculated by PreloadingTriggerType
  // and SpeculationEagerness on GetPrerenderLimitGroup.
  enum class PrerenderLimitGroup {
    kSpeculationRulesImmediate,
    kSpeculationRulesNonImmediate,
    kEmbedder,
  };

  // May be called when it is believed to be likely that the user will perform a
  // back navigation due to the trigger indicated by `predictor` (e.g. they're
  // hovering over a back button).
  void BackNavigationLikely(PreloadingPredictor predictor);

  base::WeakPtr<PrerenderHostRegistry> GetWeakPtr();

  // Only used for tests.
  base::OneShotTimer* GetEmbedderTimerForTesting() {
    return &timeout_timer_for_embedder_;
  }
  base::OneShotTimer* GetSpeculationRulesTimerForTesting() {
    return &timeout_timer_for_speculation_rules_;
  }
  void SetTaskRunnerForTesting(
      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
  bool HasOngoingHttpCacheQueryForTesting() const {
    return !!http_cache_query_loader_;
  }

  bool PrerenderCanBeStartedWhenInitiatorIsInBackground();

  // Returns true when prerendering can be triggered for embedders without
  // hitting the number limit of running prerenders.
  bool IsAllowedToStartPrerenderingForEmbedder();

  // Cancels the existing hosts for the given origin filter.
  // Currently used for browsing data removal and Clear-Site-Data header.
  void CancelHostsByOriginFilter(
      const StoragePartition::StorageKeyMatcherFunction& storage_key_filter,
      PrerenderFinalStatus final_status);

  PrerenderHostId GetPrerenderHostIdForNavigation(
      NavigationRequest* navigation_request);

 private:
  // WebContentsObserver implementation:
  void DidStartNavigation(NavigationHandle* navigation_handle) override;
  void ReadyToCommitNavigation(NavigationHandle* navigation_handle) override;
  void DidFinishNavigation(NavigationHandle* navigation_handle) override;
  void OnVisibilityChanged(Visibility visibility) override;
  void PrimaryMainFrameRenderProcessGone(
      base::TerminationStatus status) override;

  bool CancelHostInternal(FrameTreeNodeId frame_tree_node_id,
                          const PrerenderCancellationReason& reason);
  bool CancelNewTabHostInternal(FrameTreeNodeId frame_tree_node_id,
                                const PrerenderCancellationReason& reason);

  // Returns true if `navigation_request` can activate `host`.
  bool CanNavigationActivateHost(NavigationRequest& navigation_request,
                                 PrerenderHost& host);
  void DeletePendingDeletionHosts(FrameTreeNodeId prerender_host_id);
  void ScheduleToDeleteAbandonedHost(
      std::unique_ptr<PrerenderHost> prerender_host,
      const PrerenderCancellationReason& cancellation_reason);
  void SchedulePendingDeletionPrerenderNewTabHandle(
      std::unique_ptr<PrerenderNewTabHandle> handle);

  void DeleteAbandonedHosts();

  void NotifyTrigger(const GURL& url);
  void NotifyCancel(FrameTreeNodeId host_frame_tree_node_id,
                    const PrerenderCancellationReason& reason);

  // Pops one PrerenderHost from the queue and starts the prerendering if
  // there's no running prerender and `kNoFrameTreeNode` is passed as
  // `frame_tree_node_id`. If the given `frame_tree_node_id` is valid, this
  // function starts prerendering for the id. Returns starting prerender host id
  // when it succeeds, and returns an invalid FrameTreeNodeId if it's cancelled.
  FrameTreeNodeId StartPrerendering(FrameTreeNodeId frame_tree_node_id);

  // Cancels the existing hosts that were triggered by `trigger_types`.
  void CancelHostsForTriggers(std::vector<PreloadingTriggerType> trigger_types,
                              const PrerenderCancellationReason& reason);

  // Calculates PrerenderLimitGroup by PreloadingTriggerType and
  // SpeculationEagerness.
  PrerenderLimitGroup GetPrerenderLimitGroup(
      PreloadingTriggerType trigger_type,
      std::optional<blink::mojom::SpeculationEagerness> eagerness);

  // Returns the number of hosts that prerender_host_by_frame_tree_node_id_
  // holds by limit group.
  int GetHostCountByLimitGroup(PrerenderLimitGroup limit_group);

  // Returns whether a certain type of PreloadingTriggerType is allowed to be
  // added to PrerenderHostRegistry according to the limit of the given
  // PreloadingTriggerType. SpeculationEagerness is additionally considered in
  // order to apply the limits and behaviors according to
  // PrerenderLimitGroup.
  bool IsAllowedToStartPrerenderingForTrigger(
      PreloadingTriggerType trigger_type,
      std::optional<blink::mojom::SpeculationEagerness> eagerness);

  // Called when we have the HTTP cache result of the main resource of the back
  // navigation queried by `BackNavigationLikely`.
  void OnBackResourceCacheResult(
      PreloadingPredictor predictor,
      base::WeakPtr<PreloadingAttempt> attempt,
      GURL back_url,
      scoped_refptr<net::HttpResponseHeaders> headers);

  void OnMemoryPressure(
      base::MemoryPressureLevel memory_pressure_level) override;

  void RecordPotentialPrerenderProcessReuse(bool has_machable_hosts,
                                            const GURL& naivgation_url);

  // Find a prerender host that is marked as reusable and under the
  // same site as attributes.prerendering_url.
  std::unique_ptr<PrerenderHost> FindAndTakePrerenderHostToReuse(
      const PrerenderAttributes& attributes);

  scoped_refptr<base::SingleThreadTaskRunner> GetTimerTaskRunner();

  // Holds the frame_tree_node_id of running PrerenderHost. Reset to an invalid
  // value when there's no running PrerenderHost. Tracks only the host id of
  // speculation rules triggers and ignores requests from embedder because
  // embedder requests are more urgent and we'd like to handle embedder
  // prerender independently from speculation rules requests.
  FrameTreeNodeId running_prerender_host_id_;

  // Holds the ids of upcoming prerender requests. The requests from embedder
  // trigger are prioritized and pushed to the front of the queue, while the
  // requests from the speculation rules are appended to the back. This may
  // contain ids of cancelled requests. You can identify cancelled requests by
  // checking if an id is in `prerender_host_by_frame_tree_node_id_`.
  base::circular_deque<FrameTreeNodeId> pending_prerenders_;

  // Hosts that are not reserved for activation yet. This map also includes the
  // hosts still waiting for their start.
  // TODO(crbug.com/40150744): Expire prerendered contents if they are
  // not used for a while.
  base::flat_map<FrameTreeNodeId, std::unique_ptr<PrerenderHost>>
      prerender_host_by_frame_tree_node_id_;

  // Holds the host id of non-immediate prerenders by their arrival order. It is
  // used to calculate the oldest prerender on GetOldestHostPerLimitGroup.
  base::circular_deque<FrameTreeNodeId>
      non_immediate_prerender_host_id_by_arrival_order_;

  // The host that is reserved for activation.
  std::unique_ptr<PrerenderHost> reserved_prerender_host_;

  // Handles that manage WebContents for prerendering in new tabs.
  base::flat_map<FrameTreeNodeId, std::unique_ptr<PrerenderNewTabHandle>>
      prerender_new_tab_handle_by_frame_tree_node_id_;

  // Hosts that are scheduled to be deleted asynchronously.
  // Design note: PrerenderHostRegistry should explicitly manage the hosts to be
  // deleted instead of depending on the deletion helpers like DeleteSoon() to
  // asynchronously destruct them before this instance is deleted. The helpers
  // could let the hosts and their FrameTrees outlive WebContentsImpl (the owner
  // of the registry) and results in UAF.
  std::vector<std::unique_ptr<PrerenderHost>> to_be_deleted_hosts_;

  // The list of hosts which are scheduled to be deleted when
  // `DeletePendingDeletionHosts` is called. This list is for avoiding
  // the PrerenderHost being deleted prematurely before IPC calls are completed.
  base::flat_map<FrameTreeNodeId, std::unique_ptr<PrerenderHost>>
      pending_deletion_hosts_;

  // PrerenderNewTabHandle that is scheduled to be deleted asynchronously. When
  // unload related events are dispatched, PrerenderNewTabHandle deletion needs
  // to be delayed until the dispatch of the events.
  std::unique_ptr<PrerenderNewTabHandle>
      pending_deletion_new_tab_prerender_handle_;

  // Starts running the timers when prerendering gets hidden.
  base::OneShotTimer timeout_timer_for_embedder_;
  base::OneShotTimer timeout_timer_for_speculation_rules_;
  // Only used for tests. This task runner is used for precise injection in
  // tests and for timing control.
  scoped_refptr<base::SingleThreadTaskRunner> timer_task_runner_for_testing_;

  // A pending cache-only load of a URL, used to identify whether there is an
  // entry for it in the HTTP cache.
  std::unique_ptr<network::SimpleURLLoader> http_cache_query_loader_;

  base::MemoryPressureListenerRegistration
      memory_pressure_listener_registration_;

  base::ObserverList<Observer> observers_;

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

}  // namespace content

#endif  // CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_HOST_REGISTRY_H_