| // 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 <array> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/circular_deque.h" |
| #include "base/containers/flat_map.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/memory_coordinator/memory_consumer.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/common/content_export.h" |
| #include "content/common/frame.mojom-forward.h" |
| #include "content/public/browser/preloading.h" |
| #include "content/public/browser/storage_partition.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 FrameTreeNode; |
| class NavigationRequest; |
| class PrerenderCancellationReason; |
| class PrerenderHost; |
| class PrerenderNewTabHandle; |
| class RenderFrameHostImpl; |
| class StoredPage; |
| class WebContentsImpl; |
| 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::MemoryConsumer { |
| 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 and allows them to |
| // be retriggered. |
| virtual void OnRetriggerable(PrerenderHostId host_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 PrerenderHostId of the created 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. |
| PrerenderHostId 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_id_`). |
| PrerenderHostId CreateAndStartHostForNewTab( |
| const PrerenderAttributes& attributes, |
| const PreloadingPredictor& creating_predictor, |
| const PreloadingPredictor& enacting_predictor, |
| PreloadingConfidence confidence); |
| |
| // Cancels the host registered for PrerenderHostId. 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(PrerenderHostId prerender_host_id, |
| PrerenderFinalStatus final_status); |
| // Same as CancelHost, but can pass a detailed reason for recording if given. |
| bool CancelHost(PrerenderHostId prerender_host_id, |
| const PrerenderCancellationReason& reason); |
| |
| // Cancels the existing hosts specified in the vector with the same reason. |
| // Returns a subset of `prerender_host_ids` that were actually cancelled. |
| std::set<PrerenderHostId> CancelHosts( |
| const std::vector<PrerenderHostId>& prerender_host_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 PrerenderHostId of the prerendered page. |
| // 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(). |
| PrerenderHostId 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 |
| // PrerenderHostId 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, |
| PrerenderHostId 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( |
| PrerenderHostId prerender_host_id, |
| NavigationRequest& navigation_request); |
| |
| RenderFrameHostImpl* GetRenderFrameHostForReservedHost( |
| PrerenderHostId prerender_host_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(PrerenderHostId prerender_host_id); |
| |
| // Returns how many times the initiator's process has been reused for |
| // prerendering. |
| int GetProcessReuseCount() const; |
| |
| // Increments the reuse count for the initiator's process. |
| [[nodiscard]] base::ScopedClosureRunner IncrementProcessReuseCount(); |
| |
| // Returns the non-reserved host with the given id. Returns nullptr if the id |
| // does not match any non-reserved host. |
| PrerenderHost* FindNonReservedHostById(PrerenderHostId prerender_host_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 the root FrameTreeNodes of prerender hosts managed by new tab |
| // handles. These are prerenders triggered with target_hint="_blank" that live |
| // in separate WebContents from the initiator. |
| std::vector<FrameTreeNode*> GetNewTabPrerenderFrameTreeNodes(); |
| |
| // Returns whether `prerender_new_tab_handle_by_id_` has the given id. |
| bool HasNewTabHandleByIdForTesting(PrerenderHostId prerender_host_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_; |
| } |
| int GetHostCountByLimitGroupForTesting(PrerenderLimitGroup limit_group) { |
| return GetHostCountByLimitGroup(limit_group); |
| } |
| PrerenderHostId StartPrerenderingForTesting( |
| PrerenderHostId prerender_host_id) { |
| return StartPrerendering(prerender_host_id); |
| } |
| size_t GetPendingPrerendersCountForTesting() const { |
| return pending_prerenders_.size(); |
| } |
| PrerenderHostId GetPendingPrerenderFrontForTesting() const { |
| return pending_prerenders_.empty() ? PrerenderHostId() |
| : pending_prerenders_.front(); |
| } |
| int GetScaledLimitForTesting(PrerenderLimitGroup limit_group) const { |
| return GetScaledLimit(limit_group); |
| } |
| |
| 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); |
| void SchedulePendingDeletionPrerenderNewTabHandle( |
| base::PassKey<PrerenderNewTabHandle>, |
| std::unique_ptr<PrerenderNewTabHandle> handle); |
| |
| 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(PrerenderHostId prerender_host_id, |
| const PrerenderCancellationReason& reason); |
| bool CancelNewTabHostInternal(PrerenderHostId prerender_host_id, |
| const PrerenderCancellationReason& reason); |
| |
| // Returns true if `navigation_request` can activate `host`. |
| bool CanNavigationActivateHost(NavigationRequest& navigation_request, |
| PrerenderHost& host); |
| void DeletePendingDeletionHosts(PrerenderHostId prerender_host_id); |
| void ScheduleToDeleteAbandonedHost( |
| std::unique_ptr<PrerenderHost> prerender_host, |
| const PrerenderCancellationReason& cancellation_reason); |
| |
| void DeleteAbandonedHosts(); |
| |
| void NotifyTrigger(const GURL& url); |
| void NotifyRetriggerable(PrerenderHostId host_id, |
| const PrerenderCancellationReason& reason); |
| void RemoveFromArrivalOrderQueues(PrerenderHostId host_id); |
| |
| // Pops one PrerenderHost from the queue and starts the prerendering if |
| // there's no running prerender and an invalid PrerenderHostId is passed as |
| // `prerender_host_id`. If the given `prerender_host_id` is valid, this |
| // function starts prerendering for the id. Returns starting PrerenderHostId |
| // when it succeeds, and returns an invalid PrerenderHostId if it's cancelled. |
| PrerenderHostId StartPrerendering(PrerenderHostId prerender_host_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_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); |
| |
| // base::MemoryConsumer: |
| void OnUpdateMemoryLimit() override; |
| void OnReleaseMemory() override; |
| |
| // Evicts excess running prerender hosts in `limit_group` if current host |
| // count exceeds the scaled capacity limit using the limit group's eviction |
| // policy (LIFO vs. FIFO). |
| void EvictExcessHostsForLimitGroup(PrerenderLimitGroup limit_group); |
| |
| void RecordPotentialPrerenderProcessReuse(bool has_matchable_hosts, |
| const GURL& navigation_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(); |
| |
| // Returns the current memory limit. Returns base::MemoryLimit::Default() |
| // (100%) if stateful memory pressure or Prerender2 memory controls are |
| // disabled. |
| base::MemoryLimit GetCurrentMemoryLimit() const; |
| |
| // Returns the baseline unscaled maximum capacity limit for `limit_group`. |
| int GetMaxLimit(PrerenderLimitGroup limit_group) const; |
| |
| // Scales the baseline capacity limit of `limit_group` by the current memory |
| // limit percentage. |
| int GetScaledLimit(PrerenderLimitGroup limit_group) const; |
| |
| // Holds the PrerenderHostId 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. |
| PrerenderHostId 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_id_`. |
| base::circular_deque<PrerenderHostId> pending_prerenders_; |
| |
| // Hosts that are not reserved for activation yet. This map also includes the |
| // hosts still waiting for their start. |
| base::flat_map<PrerenderHostId, std::unique_ptr<PrerenderHost>> |
| prerender_host_by_id_; |
| |
| // Holds the host IDs by arrival order for each PrerenderLimitGroup. |
| std::array<base::circular_deque<PrerenderHostId>, 3> |
| arrival_order_queue_by_limit_group_; |
| |
| // 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<PrerenderHostId, std::unique_ptr<PrerenderNewTabHandle>> |
| prerender_new_tab_handle_by_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<PrerenderHostId, 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_; |
| |
| std::optional<base::MemoryConsumerRegistration> memory_consumer_registration_; |
| |
| bool is_starting_prerendering_ = false; |
| |
| base::ObserverList<Observer> observers_; |
| |
| // Decrements the reuse count for the initiator's process. |
| void DecrementProcessReuseCount(); |
| |
| // The number of prerender hosts which attempted to reuse the initiator's |
| // process. |
| int32_t process_reuse_count_ = 0; |
| |
| base::WeakPtrFactory<PrerenderHostRegistry> weak_factory_{this}; |
| }; |
| |
| } // namespace content |
| |
| #endif // CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_HOST_REGISTRY_H_ |