blob: 11a8095b8bd99404d8682340b64e16bdf66bf5cb [file] [log] [blame]
// Copyright 2022 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_PREFETCH_PREFETCH_SERVICE_H_
#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SERVICE_H_
#include <map>
#include <optional>
#include <string_view>
#include "base/dcheck_is_on.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/preloading/prefetch/no_vary_search_helper.h"
#include "content/browser/preloading/prefetch/prefetch_container.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/browser/preloading/prefetch/prefetch_serving_handle.h"
#include "content/browser/preloading/prefetch/prefetch_status.h"
#include "content/browser/preloading/prefetch/prefetch_streaming_url_loader_common_types.h"
#include "content/common/content_export.h"
#include "content/public/browser/frame_tree_node_id.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/prefetch_handle.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "net/cookies/canonical_cookie.h"
#include "net/http/http_no_vary_search_data.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"
namespace network {
class SharedURLLoaderFactory;
} // namespace network
namespace network::mojom {
class NetworkContext;
} // namespace network::mojom
namespace content {
class BrowserContext;
class PrefetchMatchResolver;
class PrefetchOriginProber;
class PrefetchProxyConfigurator;
class PrefetchScheduler;
class PrefetchServiceDelegate;
class ServiceWorkerContext;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class PrefetchRedirectResult {
kSuccessRedirectFollowed = 0,
kFailedNullPrefetch = 1,
// OBSOLETE: kFailedRedirectsDisabled = 2,
kFailedInvalidMethod = 3,
kFailedInvalidResponseCode = 4,
kFailedInvalidChangeInNetworkContext = 5,
kFailedIneligible = 6,
kFailedInsufficientReferrerPolicy = 7,
kMaxValue = kFailedInsufficientReferrerPolicy,
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class PrefetchRedirectNetworkContextTransition {
kDefaultToDefault = 0,
kDefaultToIsolated = 1,
kIsolatedToDefault = 2,
kIsolatedToIsolated = 3,
kMaxValue = kIsolatedToIsolated,
};
// Manages all prefetches within a single BrowserContext. Responsible for
// checking the eligibility of the prefetch, making the network request for the
// prefetch, and provide prefetched resources to URL loader interceptor when
// needed.
//
// `PrefetchService` is an `PrefetchContainer::Observer` to `PrefetchContainer`s
// in `owned_prefetches_`.
class CONTENT_EXPORT PrefetchService : public PrefetchContainer::Observer {
public:
static PrefetchService* GetFromFrameTreeNodeId(
FrameTreeNodeId frame_tree_node_id);
static void SetFromFrameTreeNodeIdForTesting(
FrameTreeNodeId frame_tree_node_id,
std::unique_ptr<PrefetchService> prefetch_service);
// |browser_context| must outlive this instance. In general this should always
// be true, since |PrefetchService| will be indirectly owned by
// |BrowserContext|.
explicit PrefetchService(BrowserContext* browser_context);
~PrefetchService() override;
PrefetchService(const PrefetchService&) = delete;
const PrefetchService& operator=(const PrefetchService&) = delete;
BrowserContext* GetBrowserContext() const { return browser_context_; }
PrefetchServiceDelegate* GetPrefetchServiceDelegate() const {
return delegate_.get();
}
void SetPrefetchServiceDelegateForTesting(
std::unique_ptr<PrefetchServiceDelegate> delegate);
PrefetchProxyConfigurator* GetPrefetchProxyConfigurator() const {
return prefetch_proxy_configurator_.get();
}
virtual PrefetchOriginProber* GetPrefetchOriginProber() const;
virtual void PrefetchUrl(base::WeakPtr<PrefetchContainer> prefetch_container);
// Copies any cookies in the isolated network context associated with
// |prefetch_container| to the default network context.
virtual void CopyIsolatedCookies(const PrefetchServingHandle& serving_handle);
// Adds `PrefetchContainer` under control of `PrefetchService` and returns
// PrefetchHandle so that the caller can control prefetch resources associated
// with this.
//
// `AddPrefetchContainer*()` synchronously destruct `prefetch_container` if
// the key conflicted to the one already added with migration of some
// attributes. See also `MigrateNewlyaAdded()`.
[[nodiscard]] std::unique_ptr<PrefetchHandle> AddPrefetchContainerWithHandle(
std::unique_ptr<PrefetchContainer> prefetch_container);
void AddPrefetchContainerWithoutStartingPrefetchForTesting(
std::unique_ptr<PrefetchContainer> prefetch_container);
// Returns `true` if a new prefetch request with `url` and
// `no_vary_search_hint` has a duplicate in the prefetch cache and thus the
// caller can choose not to start the prefetch request.
//
// Note: This is currently used for WebView initiated prefetches
// so consideration should be taken if updating the
// underlying implementation (or its dependencies).
bool IsPrefetchDuplicate(
GURL& url,
std::optional<net::HttpNoVarySearchData> no_vary_search_hint);
// Whether the prefetch attempt for `key` has failed or discarded.
// Note: the semantics of this method is not super clear and thus is exposed
// only for the existing `PrefetchDocumentManager` use case for now.
bool IsPrefetchAttemptFailedOrDiscardedInternal(
base::PassKey<PrefetchDocumentManager>,
PrefetchContainer::Key key) const;
// An interface to notify `PrefetchService` that the given `PrefetchContainer`
// is no longer needed from outside of the service.
void MayReleasePrefetch(base::WeakPtr<PrefetchContainer> prefetch_container);
// Called by PrefetchDocumentManager when it finishes processing the latest
// update of speculation candidates.
void OnCandidatesUpdated();
// Helper functions to control the behavior of the eligibility check when
// testing.
static void SetServiceWorkerContextForTesting(ServiceWorkerContext* context);
static void SetHostNonUniqueFilterForTesting(
bool (*filter)(std::string_view));
// Sets the URLLoaderFactory to be used during testing instead of the
// |PrefetchNetworkContext| associated with each |PrefetchContainer|. Note
// that this does not take ownership of |url_loader_factory|, and caller must
// keep ownership over the course of the test.
static void SetURLLoaderFactoryForTesting(
network::SharedURLLoaderFactory* url_loader_factory);
// Sets the NetworkContext to use just for the proxy lookup. Note that this
// does not take ownership of |network_context|, and the caller must keep
// ownership over the course of the test.
static void SetNetworkContextForProxyLookupForTesting(
network::mojom::NetworkContext* network_context);
// Set a callback for injecting delay for eligibility check in tests.
//
// Make sure to call
// `SetDelayEligibilityCheckForTesting(base::NullCallback())` at the end of an
// unit test that used this method, as this sets a global variable and it is
// shared in unit tests.
using DelayEligibilityCheckForTesting =
base::RepeatingCallback<void(base::OnceClosure)>;
static void SetDelayEligibilityCheckForTesting(
DelayEligibilityCheckForTesting callback);
// Set an ineligibility to make eligibility check always fail in tests.
static void SetForceIneligibilityForTesting(PreloadingEligibility);
base::WeakPtr<PrefetchContainer> MatchUrl(
const PrefetchContainer::Key& key) const;
std::vector<std::pair<GURL, base::WeakPtr<PrefetchContainer>>>
GetAllForUrlWithoutRefAndQueryForTesting(
const PrefetchContainer::Key& key) const;
// Evicts completed and in-progress prefetches as part of
// Clear-Site-Data header and Clearing Browser Data if the prefetch's
// referring origin matches the storage_key_filter.
void EvictPrefetchesForBrowsingDataRemoval(
const StoragePartition::StorageKeyMatcherFunction& storage_key_filter,
PrefetchStatus status);
// Returns candidate `PrefetchContainer`s and servable states for matching
// process. Corresponds to 3.4. of
// https://wicg.github.io/nav-speculation/prefetch.html#wait-for-a-matching-prefetch-record
//
// Note that `PrefetchContainer::GetServableState()` depends on
// `base::TimeTicks::now()` and can expire (can change from `kServable` to
// `kNotServable`) in the minute between two calls. Deciding something with
// multiple `PrefetchContainer::GetServableState()` calls can
// lead inconsistent state. To avoid that, we record
// `PrefetchServableState` in the `flat_map` at the beginning of
// matching process and refer to it.
std::pair<std::vector<PrefetchContainer*>,
base::flat_map<PrefetchContainer::Key, PrefetchServableState>>
CollectMatchCandidates(const PrefetchContainer::Key& key,
bool is_nav_prerender,
base::WeakPtr<PrefetchServingPageMetricsContainer>
serving_page_metrics_container);
// Exposes methods for `PrefetchScheduler`. See documentation of private
// methods with the same names.
void EvictPrefetch(base::PassKey<PrefetchScheduler>,
base::WeakPtr<PrefetchContainer> prefetch_container);
bool StartSinglePrefetch(base::PassKey<PrefetchScheduler>,
base::WeakPtr<PrefetchContainer> prefetch_container);
PrefetchScheduler& GetPrefetchSchedulerForTesting() { return *scheduler_; }
base::WeakPtr<PrefetchService> GetWeakPtr();
private:
friend class PrefetchURLLoaderInterceptorTestBase;
struct CheckEligibilityParams;
// Checks whether the given |prefetch_container| is eligible for prefetch.
// Once the eligibility is determined then |OnGotEligibility()| will be
// called.
void CheckEligibilityOfPrefetch(CheckEligibilityParams params);
void CheckHasServiceWorker(CheckEligibilityParams params);
void OnGotServiceWorkerResult(
CheckEligibilityParams params,
base::Time check_has_service_worker_start_time,
ServiceWorkerCapability service_worker_capability);
// Called after getting the existing cookies associated with
// |prefetch_container|. If there are any cookies, then the prefetch is not
// eligible.
void OnGotCookiesForEligibilityCheck(
CheckEligibilityParams params,
const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_cookies);
// Starts the check for whether or not there is a proxy configured for the URL
// of |prefetch_container|. If there is an existing proxy, then the prefetch
// is not eligible.
void StartProxyLookupCheck(CheckEligibilityParams params);
// Called after looking up the proxy configuration for the URL of
// |prefetch_container|. If there is an existing proxy, then the prefetch is
// not eligible.
void OnGotProxyLookupResult(CheckEligibilityParams params, bool has_proxy);
// Called when the eligibility is determined for each fetch of prefetch, i.e.
// initial fetch and redirects.
//
// If ineligible, these methods may convert the prefetch into decoy.
//
// If the initial fetch (respectively, the redirect) is eligible or the
// prefetch is decoy, the prefetch is added to `prefetch_queue_`
// (respectively, is retained in the queue) and proceeds to the next fetch.
void OnGotEligibilityForNonRedirect(CheckEligibilityParams params,
PreloadingEligibility eligibility);
void OnGotEligibilityForRedirect(
net::RedirectInfo redirect_info,
network::mojom::URLResponseHeadPtr redirect_head,
CheckEligibilityParams params,
PreloadingEligibility eligibility);
// Adds `prefetch_container` to the cache but doesn't initiate prefetching.
// Use `AddPrefetchContainerWithHandle()` for non-test cases.
void AddPrefetchContainerWithoutStartingPrefetch(
std::unique_ptr<PrefetchContainer> prefetch_container);
// Adds to `owned_prefetches_`.
void AddPrefetchContainerToOwnedPrefetches(
std::unique_ptr<PrefetchContainer> prefetch_container);
// Starts the network requests for as many prefetches in |prefetch_queue_| as
// possible.
void Prefetch();
// Pops the first valid prefetch (determined by PrefetchDocumentManager) from
// |prefetch_queue_|. Returns a tuple containing the popped prefetch and
// (optionally) an already completed prefetch that needs to be evicted to make
// space for the new prefetch. If there are no valid prefetches in the queue,
// then (nullptr, nullptr) is returned.
std::tuple<base::WeakPtr<PrefetchContainer>, base::WeakPtr<PrefetchContainer>>
PopNextPrefetchContainer();
// The prefetch is reset after
// `PrefetchContainerDefaultTtlInPrefetchService()`
// or the overridden TTL duration. If
// `PrefetchContainerDefaultTtlInPrefetchService()` returns a value less than
// or equal to zero, the prefetch is kept indefinitely.
void OnPrefetchTimeout(base::WeakPtr<PrefetchContainer> prefetch);
// Evict `prefetch_container` before starting a new prefetch.
//
// Precondition: `prefetch_container` must be valid.
void EvictPrefetch(base::WeakPtr<PrefetchContainer> prefetch_container);
// Starts the given |prefetch_container|.
//
// Returns true iff a prefetch is started and the caller should regard this is
// active.
//
// Precondition: `prefetch_container` must be valid.
bool StartSinglePrefetch(base::WeakPtr<PrefetchContainer> prefetch_container);
// Creates a new URL loader and starts a network request for
// |prefetch_container|. |MakePrefetchRequest| must have been previously
// called.
void SendPrefetchRequest(base::WeakPtr<PrefetchContainer> prefetch_container);
// Gets the URL loader for the given |prefetch_container|. If an override was
// set by |SetURLLoaderFactoryForTesting|, then that will be returned instead.
scoped_refptr<network::SharedURLLoaderFactory>
GetURLLoaderFactoryForCurrentPrefetch(
base::WeakPtr<PrefetchContainer> prefetch_container);
// Called when the request for |prefetch_container| is redirected.
void OnPrefetchRedirect(base::WeakPtr<PrefetchContainer> prefetch_container,
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr redirect_head);
// Called when the response for |prefetch_container| has started. Based on
// |head|, returns a status to inform the |PrefetchStreamingURLLoader| whether
// the prefetch is servable. If servable, then `std::nullopt` will be
// returned, otherwise a failure status is returned.
std::optional<PrefetchErrorOnResponseReceived> OnPrefetchResponseStarted(
base::WeakPtr<PrefetchContainer> prefetch_container,
network::mojom::URLResponseHead* head);
// PrefetchContainer::Observer overrides:
void OnWillBeDestroyed(PrefetchContainer& prefetch_container) override;
void OnGotInitialEligibility(PrefetchContainer& prefetch_container,
PreloadingEligibility eligibility) override;
void OnDeterminedHead(PrefetchContainer& prefetch_container) override;
void OnPrefetchCompletedOrFailed(
PrefetchContainer& prefetch_container,
const network::URLLoaderCompletionStatus& completion_status,
const std::optional<int>& response_code) override;
// Called when the cookies from |prefetch_conatiner| are read from the
// isolated network context and are ready to be written to the default network
// context.
void OnGotIsolatedCookiesForCopy(
PrefetchServingHandle serving_handle,
const net::CookieAccessResultList& cookie_list,
const net::CookieAccessResultList& excluded_cookies);
enum class HandlePrefetchContainerResult {
// No prefetch was available to be used.
kNotAvailable,
// There was a prefetch available but it is not usable.
kNotUsable,
// The prefetch will be served.
kToBeServed,
// The prefetch cannot be served because Cookies have changed.
kNotToBeServedCookiesChanged,
// The prefetch's head has not yet been received.
kWaitForHead
};
using FallbackToRegularNavigationWhenPrefetchNotUsable = base::StrongAlias<
class FallbackToRegularNavigationWhenPrefetchNotUsableTag,
bool>;
// Helper function for |GetPrefetchToServe| to return |prefetch_container| via
// |on_prefetch_to_serve_ready| callback in |prefetch_match_resolver|. Starts
// the cookie copy process for the given prefetch if needed, and updates its
// state.
HandlePrefetchContainerResult ReturnPrefetchToServe(
const PrefetchContainer::Key& key,
const GURL& prefetch_url,
PrefetchServingHandle serving_handle,
PrefetchMatchResolver& prefetch_match_resolver,
FallbackToRegularNavigationWhenPrefetchNotUsable
when_prefetch_not_used_fallback_to_regular_navigation =
FallbackToRegularNavigationWhenPrefetchNotUsable(true));
// Callback for non-blocking call `PrefetchContainer::StartBlockUntilHead()`.
// Waits non-redirect response header for No-Vary-Search to determine a
// potentially matching prefetch is a matching prefetch. Corresponds 3.6 in
// https://wicg.github.io/nav-speculation/prefetch.html#wait-for-a-matching-prefetch-record
//
// Once we make the decision to use a prefetch, call |PrepareToServe| and
// |GetPrefetchToServe| again in order to enforce that prefetches that are
// served are served from |prefetches_ready_to_serve_|.
void OnMaybeDeterminedHead(
const PrefetchContainer::Key& key,
base::WeakPtr<PrefetchMatchResolver> prefetch_match_resolver,
PrefetchContainer& prefetch_container);
// Helper function for |GetPrefetchToServe| which handles a
// |prefetch_container| that could potentially be served to the navigation.
HandlePrefetchContainerResult HandlePrefetchContainerToServe(
const PrefetchContainer::Key& key,
PrefetchContainer& prefetch_container,
PrefetchMatchResolver& prefetch_match_resolver);
// Checks if there is a prefetch in |owned_prefetches_| with the same URL as
// |prefetch_container| but from a different referring RenderFrameHost.
// Records the result to a UMA histogram.
void RecordExistingPrefetchWithMatchingURL(
const PrefetchContainer& prefetch_container) const;
// If `should_progress` is true, calls `PrefetchScheduler::ProgressAsync()`
// (implicitly). This argument is meaningful only if `UsePrefetchScheduler()`.
void ResetPrefetchContainer(
base::WeakPtr<PrefetchContainer> prefetch_container,
bool should_progress = true);
// Methods for scheduling
void ScheduleAndProgress(base::WeakPtr<PrefetchContainer> prefetch_container);
void ScheduleAndProgressAsync(
base::WeakPtr<PrefetchContainer> prefetch_container);
void ResetPrefetchContainerAndProgressAsync(
base::WeakPtr<PrefetchContainer> prefetch_container);
void ResetPrefetchContainersAndProgressAsync(
std::vector<base::WeakPtr<PrefetchContainer>> prefetch_containers);
// CAUTION: This doesn't call `ResetPrefetchContainer()` to preserve current
// behavior.
void RemoveFromSchedulerAndProgressAsync(
PrefetchContainer& prefetch_container);
// Returns `true` if the `prefetch_container` is stale. I.e.
// the prefetch either is not or never will be servable to a
// navigation.
//
// Note: This is currently used for WebView initiated prefetches so
// consideration should be taken if updating the underlying implementation (or
// its dependencies).
bool IsPrefetchStale(base::WeakPtr<PrefetchContainer> prefetch_container);
// Returns if the `prefetch_container` is in active set.
bool IsPrefetchContainerInActiveSet(
const PrefetchContainer& prefetch_container);
void DumpPrefetchesForDebug() const;
// Wrappers for `owned_prefetches_`. Use these wrappers and do not directly
// access `owned_prefetches_`, to avoid accidentally destructing existing
// `PrefetchContainer` e.g. by writing to `owned_prefetches_[key]`.
const std::map<PrefetchContainer::Key, std::unique_ptr<PrefetchContainer>>&
owned_prefetches() const {
return owned_prefetches_;
}
raw_ptr<BrowserContext> browser_context_;
// Delegate provided by embedder that controls specific behavior of |this|.
// May be nullptr if embedder doesn't provide a delegate.
std::unique_ptr<PrefetchServiceDelegate> delegate_;
// The custom proxy configurator for Prefetch Proxy. Only used on prefetches
// that require the proxy.
std::unique_ptr<PrefetchProxyConfigurator> prefetch_proxy_configurator_;
// The origin prober class which manages all logic for origin probing.
std::unique_ptr<PrefetchOriginProber> origin_prober_;
// A FIFO queue of prefetches that have been confirmed to be eligible but have
// not started yet.
//
// It is used only if `!UsePrefetchScheduler()`.
//
// TODO(crbug.com/406754449): Remove it.
std::vector<base::WeakPtr<PrefetchContainer>> prefetch_queue_;
// Current prefetch with an in-progress request (if any).
//
// It is used only if `!UsePrefetchScheduler()`.
//
// TODO(crbug.com/406754449): Remove it.
std::optional<PrefetchContainer::Key> active_prefetch_;
// Prefetches owned by `this`. All `PrefetchContainer`s added by
// `AddPrefetchContainer*` will be stored here.
//
// `PrefetchContainer`s in `owned_prefetches_` must be always:
// - Added by `AddPrefetchContainerToOwnedPrefetches()`.
// - Destructed either by:
// - `ResetPrefetchContainer()` or
// - `~PrefetchService()` dtor.
//
// Use `owned_prefetches()` wherever possible, to avoid unintentional
// destruction of `PrefetchContainer`s in `owned_prefetches_`.
//
// Note that `PrefetchContainer` not added to `owned_prefetches_` can be
// destroyed elsewhere even if it has a relevant `PrefetchService` (e.g. in
// `PrefetchContainer::MigrateNewlyAdded()`).
std::map<PrefetchContainer::Key, std::unique_ptr<PrefetchContainer>>
owned_prefetches_;
// Protects against Prefetch() being called recursively.
#if DCHECK_IS_ON()
bool prefetch_reentrancy_guard_ = false;
#endif
// Manages queue of prefetches, active set, and scheduling.
//
// It is used only if `UsePrefetchScheduler()`.
//
// TODO(crbug.com/406754449): Remove the last sentence.
std::unique_ptr<PrefetchScheduler> scheduler_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<PrefetchService> weak_method_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SERVICE_H_