1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
|
// 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/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);
using PassKey = base::PassKey<PrerenderHostRegistry>;
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 the root frame tree node id of the
// prerendered page, which can be used as the id of the host. Returns an
// invalid FrameTreeNodeId 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.
//
// TODO(crbug.com/40177514): Consider returning the ownership of the reserved
// host and letting NavigationRequest own it instead of PrerenderHostRegistry.
FrameTreeNodeId 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 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();
// Gets the trigger type from the reserved PrerenderHost.
PreloadingTriggerType GetPrerenderTriggerType(
FrameTreeNodeId frame_tree_node_id);
// Gets the embedder histogram suffix from the reserved PrerenderHost. Only
// used for metrics.
const std::string& GetPrerenderEmbedderHistogramSuffix(
FrameTreeNodeId frame_tree_node_id);
// Represents the group of prerender limit calculated by PreloadingTriggerType
// and SpeculationEagerness on GetPrerenderLimitGroup.
// Currently, this is used when kPrerender2NewLimitAndScheduler is enabled.
enum class PrerenderLimitGroup {
kSpeculationRulesEager,
kSpeculationRulesNonEager,
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);
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.
// Currently, this is only used under kPrerender2NewLimitAndScheduler.
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.
// If kPrerender2NewLimitAndScheduler is enabled, SpeculationEagerness is
// additionally considered to apply the new 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);
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-eager prerenders by their arrival order.
// Currently, it is used to calculate the oldest prerender on
// GetOldestHostPerLimitGroup for kPrerender2NewLimitAndScheduler.
base::circular_deque<FrameTreeNodeId>
non_eager_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_
|