// Copyright 2025 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_REQUEST_H_
#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_REQUEST_H_

#include <optional>
#include <variant>

#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "content/browser/preloading/prefetch/prefetch_key.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/browser/preloading/prefetch/prefetch_type.h"
#include "content/browser/preloading/preload_pipeline_info_impl.h"
#include "content/browser/preloading/speculation_rules/speculation_rules_tags.h"
#include "content/common/content_export.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/prefetch_request_status_listener.h"
#include "content/public/browser/preloading.h"
#include "net/http/http_no_vary_search_data.h"
#include "net/http/http_request_headers.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "third_party/blink/public/mojom/loader/referrer.mojom.h"
#include "url/origin.h"

namespace content {

class BrowserContext;
class PrefetchContainerObserver;
class PrefetchDocumentManager;
class PreloadPipelineInfo;
class PreloadPipelineInfoImpl;
class PreloadingAttempt;
class RenderFrameHostImpl;
class WebContents;
enum class PrefetchPriority;

// `PrefetchRendererInitiatorInfo` or `PrefetchBrowserInitiatorInfo` is created
// and attached to `PrefetchRequest` for a renderer-initiated or
// browser-initiated prefetch, respectively.
//
// They represents the initiator information and other request parameters
// specific to renderer-initiated or browser-initiated prefetches.

// For renderer-initiated prefetches: the initiator Document and its
// `RenderFrameHost`, `PrefetchDocumentManager`, etc.
class CONTENT_EXPORT PrefetchRendererInitiatorInfo final {
 public:
  PrefetchRendererInitiatorInfo(
      RenderFrameHostImpl& render_frame_host,
      base::WeakPtr<PrefetchDocumentManager> prefetch_document_manager);
  ~PrefetchRendererInitiatorInfo();

  // Move-only.
  PrefetchRendererInitiatorInfo(const PrefetchRendererInitiatorInfo&) = delete;
  PrefetchRendererInitiatorInfo& operator=(
      const PrefetchRendererInitiatorInfo&) = delete;
  PrefetchRendererInitiatorInfo(PrefetchRendererInitiatorInfo&&);

  GlobalRenderFrameHostId GetRenderFrameHostId() const {
    return render_frame_host_id_;
  }
  RenderFrameHostImpl* GetRenderFrameHost() const;
  PrefetchDocumentManager* prefetch_document_manager() const {
    return prefetch_document_manager_.get();
  }
  const std::optional<base::UnguessableToken>& devtools_navigation_token()
      const {
    return devtools_navigation_token_;
  }
  ukm::SourceId ukm_source_id() const { return ukm_source_id_; }

 private:
  // The RenderFrameHostId/PrefetchDocumentManager of the Document that
  // triggered the prefetch.
  GlobalRenderFrameHostId render_frame_host_id_;

  base::WeakPtr<PrefetchDocumentManager> prefetch_document_manager_;

  // A DevTools token used to identify initiator document.
  std::optional<base::UnguessableToken> devtools_navigation_token_;

  ukm::SourceId ukm_source_id_;
};

// For browser-initiated prefetches.
class CONTENT_EXPORT PrefetchBrowserInitiatorInfo final {
 public:
  PrefetchBrowserInitiatorInfo(
      const std::string& histogram_suffix,
      std::unique_ptr<PrefetchRequestStatusListener> request_status_listener);
  ~PrefetchBrowserInitiatorInfo();

  // Move-only.
  PrefetchBrowserInitiatorInfo(const PrefetchBrowserInitiatorInfo&) = delete;
  PrefetchBrowserInitiatorInfo& operator=(const PrefetchBrowserInitiatorInfo&) =
      delete;
  PrefetchBrowserInitiatorInfo(PrefetchBrowserInitiatorInfo&&);

  PrefetchContainerObserver* request_status_listener_observer() const {
    return request_status_listener_observer_.get();
  }

  const std::string& histogram_suffix() const { return histogram_suffix_; }

 private:
  // The suffix string of embedder triggers used for generating histogram
  // recorded per trigger.
  std::string histogram_suffix_;

  // `PrefetchContainerObserver` connected to `PrefetchStatusListener`.
  // Currently used for WebView initiated prefetch.
  std::unique_ptr<PrefetchContainerObserver> request_status_listener_observer_;
};

// `PrefetchRequest` represents request parameters to `PrefetchService` or
// `PrePrefetchService` to prefetch a URL.
//
// Thread model:
//
// `PrefetchRequest` is not thread safe. Can be created/destructed on any
// thread, and passed from non-UI thread to UI thread. Should be careful about
// the following:
// - `BrowserContext`, `WebContents`, `PreloadingAttempt` (WeakPtrs): these
//   should be created on UI thread and passed to this `PrefetchRequest`.
//   While destructing/passing them across threads is safe, checking their
//   validity or accessing the underlying objects should only be done on the
//   original UI thread.
// - `PreloadPipelineInfo` (`RefCountedThreadSafe` scoped_refptr):
//   Creating/destructing/passing `PreloadPipelineInfo` across thread
//   should be safe.
//   `PreloadPipelineInfo` itself is not thread safe, so shouldn't be
//   dereferenced from non-main thread, except for exceptional cases for
//   accessing const members
//   (e.g. `preload_pipeline_info_planned_max_preloading_type()`).
// - const value-type members can be safely accessed from any thread.
//
// TODO(crbug.com/452406598, crbug.com/452389538): Consider decoupling them
// to clearly show this restriction and handle them more properly. Also consider
// the model that `PrefetchRequest` is initialized without UI-thread-bound
// members on non-UI thread, and they are inserted on the UI thread later.
//
// TODO(https://crbug.com/437631382): Incrementally migrate the
// `PrefetchContainer` constructor arguments into `PrefetchRequest`, so that
// eventually all parameters are passed via `PrefetchRequest` from the current
// callers of the `PrefetchContainer` constructors. After that, we can make the
// callers to construct `PrefetchRequest` instead of `PrefetchContainer`.
class CONTENT_EXPORT PrefetchRequest final {
 public:
  // For renderer-initiated prefetch.
  static std::unique_ptr<const PrefetchRequest> CreateRendererInitiated(
      RenderFrameHostImpl& referring_render_frame_host,
      const blink::DocumentToken& referring_document_token,
      const GURL& url,
      const PrefetchType& prefetch_type,
      const blink::mojom::Referrer& referrer,
      std::optional<SpeculationRulesTags> speculation_rules_tags,
      std::optional<net::HttpNoVarySearchData> no_vary_search_hint,
      std::optional<PrefetchPriority> priority,
      base::WeakPtr<PrefetchDocumentManager> prefetch_document_manager,
      scoped_refptr<PreloadPipelineInfo> preload_pipeline_info,
      base::WeakPtr<PreloadingAttempt> attempt = nullptr);

  // For browser-initiated prefetch.
  // We can pass the referring origin of prefetches via `referring_origin` if
  // necessary.
  static std::unique_ptr<const PrefetchRequest> CreateBrowserInitiated(
      WebContents& referring_web_contents,
      const GURL& url,
      const PrefetchType& prefetch_type,
      const std::string& histogram_suffix,
      const blink::mojom::Referrer& referrer,
      const std::optional<url::Origin>& referring_origin,
      std::optional<net::HttpNoVarySearchData> no_vary_search_hint,
      std::optional<PrefetchPriority> priority,
      scoped_refptr<PreloadPipelineInfo> preload_pipeline_info,
      base::WeakPtr<PreloadingAttempt> attempt = nullptr,
      PreloadingHoldbackStatus holdback_status_override =
          PreloadingHoldbackStatus::kUnspecified,
      std::optional<base::TimeDelta> ttl = std::nullopt,
      bool should_ignore_saver_modes = false);

  // For browser-initiated prefetch that doesn't depend on web
  // contents. We can pass the referring origin of prefetches via
  // `referring_origin` if necessary.
  static std::unique_ptr<const PrefetchRequest>
  CreateBrowserInitiatedWithoutWebContents(
      BrowserContext* browser_context,
      const GURL& url,
      const PrefetchType& prefetch_type,
      const std::string& histogram_suffix,
      const blink::mojom::Referrer& referrer,
      bool javascript_enabled,
      const std::optional<url::Origin>& referring_origin,
      std::optional<net::HttpNoVarySearchData> no_vary_search_hint,
      std::optional<PrefetchPriority> priority,
      scoped_refptr<PreloadPipelineInfo> preload_pipeline_info,
      base::WeakPtr<PreloadingAttempt> attempt = nullptr,
      const net::HttpRequestHeaders& additional_headers = {},
      std::unique_ptr<PrefetchRequestStatusListener> request_status_listener =
          nullptr,
      base::TimeDelta ttl = PrefetchContainerDefaultTtlInPrefetchService(),
      bool should_append_variations_header = true,
      bool should_disable_block_until_head_timeout = false,
      bool should_bypass_http_cache = false);

  // For browser-initiated prefetch that doesn't depend on web
  // contents and main thread.
  static std::unique_ptr<const PrefetchRequest>
  CreateBrowserInitiatedWithoutWebContentsOffTheMainThread(
      base::WeakPtr<BrowserContext> browser_context,
      const GURL& url,
      const PrefetchType& prefetch_type,
      const std::string& histogram_suffix,
      const blink::mojom::Referrer& referrer,
      bool javascript_enabled,
      const std::optional<url::Origin>& referring_origin,
      std::optional<net::HttpNoVarySearchData> no_vary_search_hint,
      std::optional<PrefetchPriority> priority,
      base::WeakPtr<PreloadingAttempt> attempt = nullptr,
      const net::HttpRequestHeaders& additional_headers = {},
      std::unique_ptr<PrefetchRequestStatusListener> request_status_listener =
          nullptr,
      base::TimeDelta ttl = PrefetchContainerDefaultTtlInPrefetchService(),
      bool should_append_variations_header = true,
      bool should_disable_block_until_head_timeout = false,
      bool should_bypass_http_cache = false);

  // Use `Create*()` above instead.
  PrefetchRequest(
      base::PassKey<PrefetchRequest>,
      const PrefetchType& prefetch_type,
      const PrefetchKey& key,
      const std::optional<net::HttpNoVarySearchData> no_vary_search_hint,
      std::optional<PrefetchPriority> priority,
      scoped_refptr<PreloadPipelineInfo> preload_pipeline_info,
      base::WeakPtr<PreloadingAttempt> attempt,
      base::WeakPtr<WebContents> referring_web_contents,
      bool is_javascript_enabled,
      const blink::mojom::Referrer& initial_referrer,
      const std::optional<url::Origin>& referring_origin,
      base::WeakPtr<BrowserContext> browser_context,
      std::optional<SpeculationRulesTags> speculation_rules_tags,
      const net::HttpRequestHeaders& additional_headers,
      base::TimeDelta ttl,
      PreloadingHoldbackStatus holdback_status_override,
      bool should_append_variations_header,
      bool should_disable_block_until_head_timeout,
      bool should_bypass_http_cache,
      bool should_ignore_saver_modes,
      std::variant<PrefetchRendererInitiatorInfo, PrefetchBrowserInitiatorInfo>
          info);

  ~PrefetchRequest();

  const PrefetchType& prefetch_type() const { return prefetch_type_; }
  const PrefetchKey& key() const { return key_; }
  const std::optional<net::HttpNoVarySearchData>& no_vary_search_hint() const {
    return no_vary_search_hint_;
  }
  const std::optional<PrefetchPriority>& priority() const { return priority_; }

  // Can only be accessed its mutable methods/members on the UI thread.
  PreloadPipelineInfoImpl& preload_pipeline_info() const {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    return *preload_pipeline_info_;
  }

  // Const members of `PreloadPipelineInfoImpl` can be accessed from any thread.
  PreloadingType preload_pipeline_info_planned_max_preloading_type() const {
    return preload_pipeline_info_->planned_max_preloading_type();
  }

  // Can only be accessed its methods/members on the UI thread.
  PreloadingAttempt* attempt() const {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    return attempt_.get();
  }

  bool is_javascript_enabled() const { return is_javascript_enabled_; }
  const blink::mojom::Referrer& initial_referrer() const {
    return initial_referrer_;
  }
  const std::optional<url::Origin>& referring_origin() const {
    return referring_origin_;
  }

  // Can only be accessed its methods/members on the UI thread.
  const base::WeakPtr<WebContents>& referring_web_contents() const {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    return referring_web_contents_;
  }

  // Can only be accessed its methods/members on the UI thread.
  BrowserContext* browser_context() const {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    return browser_context_.get();
  }

  const std::optional<SpeculationRulesTags>& speculation_rules_tags() const {
    return speculation_rules_tags_;
  }

  const net::HttpRequestHeaders& additional_headers() const {
    return additional_headers_;
  }
  const base::TimeDelta& ttl() const { return ttl_; }
  PreloadingHoldbackStatus holdback_status_override() const {
    return holdback_status_override_;
  }
  bool should_append_variations_header() const {
    return should_append_variations_header_;
  }
  bool should_disable_block_until_head_timeout() const {
    return should_disable_block_until_head_timeout_;
  }
  // TODO(crbug.com/455296998): Remove this code for M145.
  bool should_bypass_http_cache() const { return should_bypass_http_cache_; }
  bool should_ignore_saver_modes() const { return should_ignore_saver_modes_; }

  // Returns non-null if renderer-initiated/browser-initiated, respectively.
  // Exactly one of them returns non-null.
  const PrefetchRendererInitiatorInfo* GetRendererInitiatorInfo() const;
  const PrefetchBrowserInitiatorInfo* GetBrowserInitiatorInfo() const;

  // Whether or not the prefetch proxy would be required to prefetch a URL in
  // the given `request_url_origin` based on `prefetch_type_`.
  bool IsProxyRequiredForURL(const url::Origin& request_url_origin) const;

  // Whether or not the given origin would become a cross-site/cross-origin
  // request.
  bool IsCrossSiteRequest(const url::Origin& origin) const;
  bool IsCrossOriginRequest(const url::Origin& origin) const;

  // Whether or not an isolated network context is required to prefetch `url`.
  bool IsIsolatedNetworkContextRequired(const GURL& url) const;

 private:
  // The type of this prefetch. This controls some specific details about how
  // the prefetch is handled, including whether an isolated network context or
  // the default network context is used to perform the prefetch, whether or
  // not the preftch proxy is used, and whether or not subresources are
  // prefetched.
  const PrefetchType prefetch_type_;

  // The key used to match this PrefetchContainer, including the URL that was
  // requested to prefetch.
  const PrefetchKey key_;

  // The No-Vary-Search hint of the prefetch, which is specified by the
  // speculation rules and can be different from actual
  // `PrefetchContainer::no_vary_search_data_`.
  const std::optional<net::HttpNoVarySearchData> no_vary_search_hint_;

  // An optimization hint indicating how quickly this prefetch should be
  // available.
  const std::optional<PrefetchPriority> priority_;

  // The primary `PreloadPipelineInfo`, i.e. that of the first initiator.
  // Subsequent requests' `PreloadPipelineInfo`s are tracked by
  // `PrefetchContainer::inherited_preload_pipeline_infos_`.
  // The pointer is immutable (i.e. pointing to the same
  // `PreloadPipelineInfoImpl` throughout the lifetime) while the pointed
  // PreloadPipelineInfoImpl is mutable as we'll update/notify the
  // PreloadPipelineInfoImpl.
  const scoped_refptr<PreloadPipelineInfoImpl> preload_pipeline_info_;

  // `PreloadingAttempt` is used to track the lifecycle of the preloading event,
  // and reports various statuses to UKM dashboard. It is initialised along with
  // `this`, and destroyed when `WCO::DidFinishNavigation` is fired.
  // `attempt_`'s eligibility is set in `OnEligibilityCheckComplete`, and its
  // holdback status, triggering outcome and failure reason are set in
  // `SetPrefetchStatus`.
  const base::WeakPtr<PreloadingAttempt> attempt_;

  // Whether JavaScript is on in this contents (or was, when this prefetch
  // started). This affects Client Hints behavior. Per-origin settings are
  // handled later, according to
  // |ClientHintsControllerDelegate::IsJavaScriptAllowed|.
  const bool is_javascript_enabled_;

  // The referrer to use for the initial request.
  // Only for initialization of `PrefetchContainer::resource_request_`'s
  // `referrer` / `referrer_policy`, which are updated upon redirects.
  const blink::mojom::Referrer initial_referrer_;

  // The origin and URL that initiates the prefetch request.
  // For renderer-initiated prefetch, this is calculated by referring
  // RenderFrameHost's LastCommittedOrigin. For browser-initiated prefetch, this
  // is sometimes explicitly passed via ctor.
  const std::optional<url::Origin> referring_origin_;

  // The initiator WebContents used for the source of some headers, e.g.,
  // User-Agent. This is unavailable in browser initiated prefetch that is not
  // associated with WebContents. This is for an initial guess and shouldn't be
  // used without a plan for the header validation (crbug.com/444065296).
  base::WeakPtr<WebContents> referring_web_contents_;

  // The |BrowserContext| in which this is being run.
  const base::WeakPtr<BrowserContext> browser_context_;

  // -------- Parameters that can have non-default values only for
  // -------- renderer-initiated prefetches:

  // The tags of the speculation rules that triggered this prefetch, and this
  // field is non-null if and only if this is created by SpeculationRules
  // prefech. These are assumed to have been validated by the time this is
  // constructed.
  const std::optional<SpeculationRulesTags> speculation_rules_tags_;

  // -------- Parameters that can have non-default values only for
  // -------- browser-initiated prefetches:

  // Additional headers for WebView initiated prefetch.
  // This must be empty for non-WebView initiated prefetches.
  // TODO(crbug.com/369859822): Revisit the semantics if needed.
  const net::HttpRequestHeaders additional_headers_;

  // Time-to-live (TTL) for this prefetched data. Currently, this is configured
  // for browser-initiated prefetch that doesn't depend on web content.
  // Default value is `PrefetchContainerDefaultTtlInPrefetchService()`.
  const base::TimeDelta ttl_;

  // If not `PreloadingHoldbackStatus::kUnspecified`, this value is used to
  // override holdback status derived by the normal process. It is set to
  // `attempt_` on PrefetchService::CheckAndSetPrefetchHoldbackStatus(). Default
  // value is `PreloadingHoldbackStatus::kUnspecified`.
  const PreloadingHoldbackStatus holdback_status_override_;

  // Whether to add the X-Client-Data header with experiment IDs from field
  // trials. This will not be applied to redirects. Currently, this is
  // configured for browser-initiated prefetch that doesn't depend on web
  // content. Default value is `true`.
  const bool should_append_variations_header_;

  // Whether the caller of prefetches requests to disable
  // `BlockUntilHeadTimeout`, which is currently calculated by
  // `PrefetchBlockUntilHeadTimeout()` as a `prefetch_params`.
  // Default value is `false`.
  const bool should_disable_block_until_head_timeout_;

  // Whether the prefetch request should use the HTTP cache.
  // Default value is `false`.
  // TODO(crbug.com/455296998): Remove this code for M145.
  const bool should_bypass_http_cache_;

  // If true, saver modes (e.g. Battery Saver or Data Saver) will be ignored for
  // this prefetch request.
  const bool should_ignore_saver_modes_;

  const std::variant<PrefetchRendererInitiatorInfo,
                     PrefetchBrowserInitiatorInfo>
      initiator_info_;
};

}  // namespace content

#endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_REQUEST_H_
