// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_H_
#define CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_H_

#include <map>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>

#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/predictors/loading_data_collector.h"
#include "chrome/browser/predictors/predictors_traffic_annotations.h"
#include "chrome/browser/predictors/prefetch_manager.h"
#include "chrome/browser/predictors/resource_prefetch_predictor.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/preconnect_manager.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/origin.h"

class Profile;

namespace base {
class UnguessableToken;
}  // namespace base

namespace features {

BASE_DECLARE_FEATURE(kSuppressesLoadingPredictorOnSlowNetwork);

extern const base::FeatureParam<base::TimeDelta>
    kSuppressesLoadingPredictorOnSlowNetworkThreshold;

}  // namespace features

namespace predictors {

class ResourcePrefetchPredictor;
class LoadingStatsCollector;
class PrewarmHttpDiskCacheManager;

// Entry point for the Loading predictor.
// From a high-level request (GURL and motivation) and a database of historical
// data, initiates predictive actions to speed up page loads.
//
// Also listens to main frame requests/redirects/responses to initiate and
// cancel page load hints.
//
// See ResourcePrefetchPredictor for a description of the resource prefetch
// predictor.
//
// All methods must be called from the UI thread.
class LoadingPredictor : public KeyedService,
                         public content::PreconnectManager::Delegate,
                         public PrefetchManager::Delegate {
 public:
  LoadingPredictor(const LoadingPredictorConfig& config, Profile* profile);

  LoadingPredictor(const LoadingPredictor&) = delete;
  LoadingPredictor& operator=(const LoadingPredictor&) = delete;

  ~LoadingPredictor() override;

  // Hints that a page load is expected for |url|, with the hint coming from a
  // given |origin|. If |preconnect_prediction| is provided, this will use it
  // over local predictions to trigger actions, such as prefetch and/or
  // preconnect. Returns true if no more preconnect actions should be taken by
  // the caller.
  bool PrepareForPageLoad(
      const std::optional<url::Origin>& initiator_origin,
      const GURL& url,
      HintOrigin origin,
      base::UnguessableToken network_restrictions_id,
      bool preconnectable = false,
      std::optional<PreconnectPrediction> preconnect_prediction = std::nullopt,
      content::GlobalRenderFrameHostId initiator_frame_id =
          content::GlobalRenderFrameHostId());

  // Indicates that a page load hint is no longer active.
  void CancelPageLoadHint(const GURL& url);

  // Starts initialization, will complete asynchronously.
  void StartInitialization();

  // Don't use, internal only.
  ResourcePrefetchPredictor* resource_prefetch_predictor();
  LoadingDataCollector* loading_data_collector();
  content::PreconnectManager* preconnect_manager();
  PrefetchManager* prefetch_manager();

  // KeyedService:
  void Shutdown() override;
  bool WasShutdown() { return shutdown_; }

  // OnNavigationStarted is invoked when navigation |navigation_id| with
  // |main_frame_url| has started navigating.
  void OnNavigationStarted(NavigationId navigation_id,
                           ukm::SourceId ukm_source_id,
                           const GURL& main_frame_url,
                           base::TimeTicks creation_time);
  void OnNavigationFinished(NavigationId navigation_id,
                            const GURL& old_main_frame_url,
                            const GURL& new_main_frame_url,
                            bool is_error_page);

  base::WeakPtr<LoadingPredictor> GetWeakPtr() {
    return weak_factory_.GetWeakPtr();
  }

  // content::PreconnectManager::Delegate:
  void PreconnectInitiated(const GURL& url,
                           const GURL& preconnect_url) override;
  void PreconnectFinished(
      std::unique_ptr<content::PreconnectStats> stats) override;
  bool IsPreconnectEnabled() override;

  // PrefetchManager::Delegate:
  void PrefetchInitiated(const GURL& url, const GURL& prefetch_url) override;
  void PrefetchFinished(std::unique_ptr<PrefetchStats> stats) override;

  size_t GetActiveHintsSizeForTesting() { return active_hints_.size(); }
  size_t GetTotalHintsActivatedForTesting() { return total_hints_activated_; }
  size_t GetActiveNavigationsSizeForTesting() {
    return active_navigations_.size();
  }

  const std::map<GURL, base::TimeTicks>& active_hints_for_testing() const {
    return active_hints_;
  }

  // May start a preconnect for `url`, if the current profile settings allow to
  // perform preresolve and preconnect actions. When `traffic_annotation` is
  // set, it will use the value over the default
  // `kLoadingPredictorPreconnectTrafficAnnotation`, later passed on to //net.
  // Virtual for testing.
  virtual void PreconnectURLIfAllowed(
      const GURL& url,
      bool allow_credentials,
      const net::NetworkAnonymizationKey& network_anonymization_key,
      const base::UnguessableToken& network_restrictions_id,
      const net::NetworkTrafficAnnotationTag& traffic_annotation =
          kLoadingPredictorPreconnectTrafficAnnotation,
      const content::StoragePartitionConfig* storage_partition_config =
          nullptr);

  void MaybePrewarmResources(const std::optional<url::Origin>& initiator_origin,
                             const GURL& top_frame_main_resource_url);

  bool IsLCPPTestingEnabled() const { return is_lcpp_testing_enabled_; }

 private:
  // Stores the information necessary to keep track of the active navigations.
  struct NavigationInfo {
    GURL main_frame_url;
    base::TimeTicks creation_time;
  };

  struct PreconnectData {
    url::Origin last_origin_;
    base::TimeTicks last_preconnect_time_;
    base::TimeTicks last_preresolve_time_;
  };

  // Cancels an active hint, from its iterator inside |active_hints_|. If the
  // iterator is .end(), does nothing. Returns the iterator after deletion of
  // the entry.
  std::map<GURL, base::TimeTicks>::iterator CancelActiveHint(
      std::map<GURL, base::TimeTicks>::iterator hint_it);
  void CleanupAbandonedHintsAndNavigations(NavigationId navigation_id);

  // May start preconnect and preresolve jobs according to `prediction` for
  // `url`.
  //
  // When LoadingPredictorPrefetch is enabled, starts prefetch jobs if
  // `prediction` has prefetch requests.
  void MaybeAddPreconnect(const GURL& url, PreconnectPrediction prediction);
  // If a preconnect or prefetch exists for `url`, stop it.
  void MaybeRemovePreconnect(const GURL& url);

  // May start a preconnect or a preresolve for `url`. `preconnectable`
  // indicates if preconnect is possible, or only preresolve will be performed.
  bool HandleHintByOrigin(
      const GURL& url,
      bool preconnectable,
      bool only_allow_https,
      PreconnectData& preconnect_data,
      base::UnguessableToken network_restrictions_id);

  // For testing.
  void set_mock_resource_prefetch_predictor(
      std::unique_ptr<ResourcePrefetchPredictor> predictor) {
    resource_prefetch_predictor_ = std::move(predictor);
  }

  // For testing.
  void set_mock_preconnect_manager(
      std::unique_ptr<content::PreconnectManager> preconnect_manager) {
    preconnect_manager_ = std::move(preconnect_manager);
  }

  // For testing.
  void set_mock_loading_data_collector(
      std::unique_ptr<LoadingDataCollector> loading_data_collector) {
    loading_data_collector_ = std::move(loading_data_collector);
  }

  // For LCPP testing.
  void EnableLCPPTesting() { is_lcpp_testing_enabled_ = true; }

  LoadingPredictorConfig config_;
  raw_ptr<Profile, DanglingUntriaged> profile_;
  std::unique_ptr<ResourcePrefetchPredictor> resource_prefetch_predictor_;
  std::unique_ptr<LoadingStatsCollector> stats_collector_;
  std::unique_ptr<LoadingDataCollector> loading_data_collector_;
  std::unique_ptr<content::PreconnectManager> preconnect_manager_;
  std::unique_ptr<PrefetchManager> prefetch_manager_;
  std::unique_ptr<PrewarmHttpDiskCacheManager> prewarm_http_disk_cache_manager_;
  std::map<GURL, base::TimeTicks> active_hints_;
  std::map<NavigationId, NavigationInfo> active_navigations_;
  std::map<GURL, std::set<NavigationId>> active_urls_to_navigations_;
  bool shutdown_ = false;
  size_t total_hints_activated_ = 0;

  PreconnectData omnibox_preconnect_data_;

  PreconnectData bookmark_bar_preconnect_data_;

  PreconnectData new_tab_page_preconnect_data_;

  bool is_lcpp_testing_enabled_ = false;

  friend class LoadingPredictorTest;
  friend class LoadingPredictorPreconnectTest;
  friend class LoadingPredictorTabHelperTest;
  friend class LoadingPredictorTabHelperTestCollectorTest;
  friend class LCPPTimingPredictorTestBase;
  friend class NetworkHintsHandlerImplTest;
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestMainFrameResponseCancelsHint);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestMainFrameRequestCancelsStaleNavigations);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestMainFrameResponseClearsNavigations);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestMainFrameRequestDoesntCancelExternalHint);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestDuplicateHintAfterPreconnectCompleteCalled);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestDuplicateHintAfterPreconnectCompleteNotCalled);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest,
                           TestDontTrackNonPrefetchableUrls);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorTest, TestDontPredictOmniboxHints);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorPreconnectTest,
                           TestHandleHintWithOpaqueOrigins);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorPreconnectTest,
                           TestHandleHintWhenOnlyHttpsAllowed);
  FRIEND_TEST_ALL_PREFIXES(LoadingPredictorPreconnectTest,
                           TestHandleHintPreresolveWhenOnlyHttpsAllowed);

  base::WeakPtrFactory<LoadingPredictor> weak_factory_{this};
};

}  // namespace predictors

#endif  // CHROME_BROWSER_PREDICTORS_LOADING_PREDICTOR_H_
