blob: 5fbf0420aca08de4553543860d14acd023b6c9eb [file] [log] [blame]
// 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:
// 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::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
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::MemoryPressureListener memory_pressure_listener_;
base::ObserverList<Observer> observers_;
base::WeakPtrFactory<PrerenderHostRegistry> weak_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_HOST_REGISTRY_H_