// 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.

#import "ios/chrome/browser/prerender/model/prerender_browser_agent.h"

#import <utility>

#import "base/auto_reset.h"
#import "base/check.h"
#import "base/check_deref.h"
#import "base/check_op.h"
#import "base/functional/bind.h"
#import "base/functional/callback.h"
#import "base/functional/callback_helpers.h"
#import "base/ios/device_util.h"
#import "base/memory/raw_ptr.h"
#import "base/memory/raw_ref.h"
#import "base/metrics/histogram_functions.h"
#import "base/task/bind_post_task.h"
#import "base/task/sequenced_task_runner.h"
#import "base/time/time.h"
#import "components/prefs/pref_service.h"
#import "components/signin/ios/browser/account_consistency_service.h"
#import "components/signin/ios/browser/manage_accounts_delegate.h"
#import "ios/chrome/browser/app_launcher/model/app_launcher_tab_helper.h"
#import "ios/chrome/browser/crash_report/model/crash_report_helper.h"
#import "ios/chrome/browser/history/model/history_tab_helper.h"
#import "ios/chrome/browser/itunes_urls/model/itunes_urls_handler_tab_helper.h"
#import "ios/chrome/browser/prerender/model/prerender_browser_agent_delegate.h"
#import "ios/chrome/browser/prerender/model/prerender_tab_helper.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/utils/mime_type_util.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/signin/model/account_consistency_service_factory.h"
#import "ios/chrome/browser/supervised_user/model/supervised_user_capabilities.h"
#import "ios/chrome/browser/tabs/model/tab_helper_util.h"
#import "ios/chrome/browser/web/model/load_timing_tab_helper.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/navigation/web_state_policy_decider.h"
#import "ios/web/public/ui/java_script_dialog_presenter.h"
#import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_delegate.h"
#import "ios/web/public/web_state_observer.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/network_change_notifier.h"

namespace {

// The name of the histogram for recording final status (e.g. used/cancelled)
// of prerender requests.
constexpr char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";

// Histogram to record that the load was complete when the prerender was used.
// Not recorded if the pre-render isn't used.
constexpr char kPrerenderLoadComplete[] = "Prerender.PrerenderLoadComplete";

// The name of the histogram for recording time until a successful prerender.
constexpr char kPrerenderPrerenderTimeSaved[] = "Prerender.PrerenderTimeSaved";

// Returns whether `url` can be pre-rendered.
bool CanPrerenderUrl(const GURL& url) {
  return url.is_valid() && url.SchemeIsHTTPOrHTTPS();
}

// Returns the delay before starting pre-rendering according to `policy`
base::TimeDelta DelayForPolicy(PrerenderBrowserAgent::PrerenderPolicy policy) {
  switch (policy) {
    case PrerenderBrowserAgent::PrerenderPolicy::kNoDelay:
      return base::TimeDelta();

    case PrerenderBrowserAgent::PrerenderPolicy::kDefaultDelay:
      return base::Milliseconds(500);
  }
}

// Returns the value of the NetworkPredictionSetting in `prefs`.
prerender_prefs::NetworkPredictionSetting NetworkPredictionSettingFromPrefs(
    PrefService* prefs) {
  using prerender_prefs::NetworkPredictionSetting;
  switch (prefs->GetInteger(prefs::kNetworkPredictionSetting)) {
    case std::to_underlying(NetworkPredictionSetting::kDisabled):
      return NetworkPredictionSetting::kDisabled;
    case std::to_underlying(NetworkPredictionSetting::kEnabledWifiOnly):
      return NetworkPredictionSetting::kEnabledWifiOnly;
    case std::to_underlying(NetworkPredictionSetting::kEnabledWifiAndCellular):
      return NetworkPredictionSetting::kEnabledWifiAndCellular;
    default:
      // If the value stored in the PrefService is unexpected, return
      // NetworkPredictionSetting::kDisabled (static_cast<...> would
      // lead to undefined behaviour when comparing the enum).
      return NetworkPredictionSetting::kDisabled;
  }
}

}  // anonymous namespace

// Used to observe UIApplicationDidReceiveMemoryWarningNotification from
// the NSNotificationCenter.
@interface PrerenderBrowserAgentMemoryWarningObservation : NSObject

// Will invoked `closure` when the NSNotificationCenter sends the
// UIApplicationDidReceiveMemoryWarningNotification. The closure
// will be called on the sequence the object is created.
- (instancetype)initWithClosure:(base::RepeatingClosure)closure
    NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;

@end

@implementation PrerenderBrowserAgentMemoryWarningObservation {
  base::RepeatingClosure _closure;
}

- (instancetype)initWithClosure:(base::RepeatingClosure)closure {
  if ((self = [super init])) {
    // Since NSNotificationCenter may call the selector on a background
    // thread use base::BindPostTask(...) to ensure that the closure is
    // always called on the correct sequence.
    _closure = base::BindPostTask(
        base::SequencedTaskRunner::GetCurrentDefault(), std::move(closure));

    [[NSNotificationCenter defaultCenter]
        addObserver:self
           selector:@selector(didReceiveMemoryWarning)
               name:UIApplicationDidReceiveMemoryWarningNotification
             object:nil];
  }
  return self;
}

- (void)didReceiveMemoryWarning {
  _closure.Run();
}

@end

// Information about a request.
class PrerenderBrowserAgent::RequestInfos {
 public:
  RequestInfos(const GURL& url,
               const web::Referrer& referrer,
               ui::PageTransition transition)
      : url_(url), referrer_(referrer), transition_(transition) {}

  ~RequestInfos() = default;

  const GURL& url() const { return url_; }
  const web::Referrer& referrer() const { return referrer_; }
  ui::PageTransition transition() const { return transition_; }

  web::NavigationManager::WebLoadParams load_params() const {
    web::NavigationManager::WebLoadParams params(url_);
    params.referrer = referrer_;
    params.transition_type = transition_;
    return params;
  }

 private:
  const GURL url_;
  const web::Referrer referrer_;
  const ui::PageTransition transition_;
};

// Represents a request.
template <typename WebStatePtr>
class PrerenderBrowserAgent::Request {
 public:
  Request(WebStatePtr web_state_ptr, RequestInfos infos)
      : web_state_(std::move(web_state_ptr)), infos_(std::move(infos)) {}

  ~Request() = default;

  web::WebState* web_state() const { return web_state_.get(); }
  const RequestInfos& infos() const { return infos_; }

  WebStatePtr Release() { return std::move(web_state_); }

 private:
  WebStatePtr web_state_;
  RequestInfos infos_;
};

// WebStateDelegate used by PrerenderBrowserAgent.
class PrerenderBrowserAgent::Delegate final
    : public web::WebStateDelegate,
      public web::JavaScriptDialogPresenter {
 public:
  Delegate(web::WebState* web_state, PrerenderBrowserAgent* agent)
      : web_state_(CHECK_DEREF(web_state)), agent_(CHECK_DEREF(agent)) {
    web_state_->SetDelegate(this);
  }

  ~Delegate() final { web_state_->SetDelegate(nullptr); }

  // web::WebStateDelegate implementation.
  web::WebState* CreateNewWebState(web::WebState* web_state,
                                   const GURL& url,
                                   const GURL& opener_url,
                                   bool user_initiated) final {
    agent_->ScheduleCancelPrerender();
    return nullptr;
  }

  web::JavaScriptDialogPresenter* GetJavaScriptDialogPresenter(
      web::WebState* web_state) final {
    return this;
  }

  void OnAuthRequired(web::WebState* web_state,
                      NSURLProtectionSpace* protection_space,
                      NSURLCredential* proposed_credential,
                      HTTPAuthCallback callback) final {
    agent_->ScheduleCancelPrerender();
    std::move(callback).Run(nil, nil);
  }

  void OnAuthRequired(web::WebState* web_state,
                      NSURLProtectionSpace* protection_space,
                      ClientCertAuthCallback callback) final {
    agent_->ScheduleCancelPrerender();
    std::move(callback).Run(nil);
  }

  UIView* GetWebViewContainer(web::WebState* web_state) final {
    return [agent_->delegate_ webViewContainer];
  }

  // web::JavaScriptDialogPresenter implementation.
  void RunJavaScriptAlertDialog(web::WebState* web_state,
                                const url::Origin& origin,
                                NSString* message_text,
                                base::OnceClosure callback) final {
    agent_->ScheduleCancelPrerender();
    std::move(callback).Run();
  }

  void RunJavaScriptConfirmDialog(
      web::WebState* web_state,
      const url::Origin& origin,
      NSString* message_text,
      base::OnceCallback<void(bool)> callback) final {
    agent_->ScheduleCancelPrerender();
    std::move(callback).Run(/*success=*/false);
  }

  void RunJavaScriptPromptDialog(
      web::WebState* web_state,
      const url::Origin& origin,
      NSString* message_text,
      NSString* default_prompt_text,
      base::OnceCallback<void(NSString*)> callback) final {
    agent_->ScheduleCancelPrerender();
    std::move(callback).Run(/*user_input=*/nil);
  }

  void CancelDialogs(web::WebState* web_state) override {}

 private:
  const raw_ref<web::WebState> web_state_;
  const raw_ref<PrerenderBrowserAgent> agent_;
};

// WebStateObserver used by PrerenderBrowserAgent.
class PrerenderBrowserAgent::Observer final : public web::WebStateObserver {
 public:
  Observer(web::WebState* web_state, PrerenderBrowserAgent* agent)
      : web_state_(CHECK_DEREF(web_state)),
        agent_(CHECK_DEREF(agent)),
        start_time_(base::TimeTicks::Now()) {
    web_state_->AddObserver(this);
  }

  ~Observer() final { web_state_->RemoveObserver(this); }

  // Returns whether the load was successfully completed.
  bool load_completed() const { return load_completed_; }

  // Returns the time saved by pre-rendering.
  base::TimeDelta time_saved() const {
    if (load_completed_) {
      return time_saved_;
    }

    return base::TimeTicks::Now() - start_time_;
  }

  // Whether the pre-render should be cancelled based on the mime type of
  // the content.
  bool ShouldCancelPrerenderForMimeType(std::string_view mime_type) const {
    // Cancel pre-rendering if response is
    //  -  "application/octet-stream" can be a video which should not be
    //      played for pre-rendered tab (see https://crbug.com/436813)
    //
    //  - "application/pdf" as starting in iOS 13.0, PDFs get focus on load,
    //    preventing typing in the omnibox (see https://crbug.com/1017352).
    return mime_type == kBinaryDataMimeType ||
           mime_type == kAdobePortableDocumentFormatMimeType;
  }

  // web::WebStateObserver implementation.
  void DidFinishNavigation(web::WebState* web_state,
                           web::NavigationContext* context) final {
    if (ShouldCancelPrerenderForMimeType(web_state_->GetContentsMimeType())) {
      agent_->ScheduleCancelPrerender();
    }
  }

  void PageLoaded(web::WebState* web_state,
                  web::PageLoadCompletionStatus status) final {
    if (ShouldCancelPrerenderForMimeType(web_state_->GetContentsMimeType())) {
      agent_->ScheduleCancelPrerender();
      return;
    }

    load_completed_ = status == web::PageLoadCompletionStatus::SUCCESS;
    time_saved_ = base::TimeTicks::Now() - start_time_;
  }

 private:
  const raw_ref<web::WebState> web_state_;
  const raw_ref<PrerenderBrowserAgent> agent_;

  const base::TimeTicks start_time_;
  base::TimeDelta time_saved_;
  bool load_completed_ = false;
};

// WebStatePolicyDecider used by PrerenderBrowserAgent.
class PrerenderBrowserAgent::PolicyDecider final
    : public web::WebStatePolicyDecider {
 public:
  PolicyDecider(web::WebState* web_state, PrerenderBrowserAgent* agent)
      : web::WebStatePolicyDecider(web_state), agent_(CHECK_DEREF(agent)) {}

  // WebStatePolicyDecided implementation.
  void ShouldAllowRequest(NSURLRequest* request,
                          RequestInfo request_info,
                          PolicyDecisionCallback callback) final {
    // Don't allow preloading for requests that are handled by opening another
    // application of by presenting a native UI.
    const GURL url = net::GURLWithNSURL(request.URL);
    if (!AppLauncherTabHelper::IsAppUrl(url) &&
        !ITunesUrlsHandlerTabHelper::CanHandleUrl(url)) {
      std::move(callback).Run(PolicyDecision::Allow());
      return;
    }

    std::move(callback).Run(PolicyDecision::Cancel());
    agent_->ScheduleCancelPrerender();
  }

 private:
  const raw_ref<PrerenderBrowserAgent> agent_;
};

// ManageAccountsDelegate used by PrerenderBrowserAgent.
class PrerenderBrowserAgent::ManageAccountsDelegate final
    : public ::ManageAccountsDelegate {
 public:
  explicit ManageAccountsDelegate(PrerenderBrowserAgent* agent)
      : agent_(CHECK_DEREF(agent)) {}

  // ManageAccountsDelegate implementation.
  void OnRestoreGaiaCookies() final { agent_->ScheduleCancelPrerender(); }
  void OnManageAccounts(const GURL& url, web::WebState* web_state) final {
    agent_->ScheduleCancelPrerender();
  }
  void OnAddAccount(const GURL& url,
                    const std::string& prefilled_email,
                    web::WebState* web_state) final {
    agent_->ScheduleCancelPrerender();
  }
  void OnShowConsistencyPromo(const GURL& url, web::WebState* web_state) final {
    agent_->ScheduleCancelPrerender();
  }
  void OnGoIncognito(const GURL& url, web::WebState* web_state) final {
    agent_->ScheduleCancelPrerender();
  }
  bool SigninEnabled() const final {
    return agent_->signin_enabled_data_source_->SigninEnabled();
  }

 private:
  const raw_ref<PrerenderBrowserAgent> agent_;
};

// PrerenderFinalStatus values are used in the "Prerender.FinalStatus" histogram
// and new values needs to be kept in sync with histogram.xml.
enum class PrerenderBrowserAgent::PrerenderFinalStatus {
  kUsed = 0,
  kMemoryLimitExceeded = 12,
  kCancelled = 32,
  kNotAllowed = 63,
  kMaxValue = kNotAllowed,
};

PrerenderBrowserAgent::PrerenderBrowserAgent(
    Browser* browser,
    signin::SigninEnabledDataSource* signin_enabled_data_source)
    : BrowserUserData(browser),
      signin_enabled_data_source_(signin_enabled_data_source) {
  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
  registrar_.Init(browser_->GetProfile()->GetPrefs());
  registrar_.Add(prefs::kNetworkPredictionSetting,
                 base::BindRepeating(
                     &PrerenderBrowserAgent::OnNetworkPredictionSettingChanged,
                     weak_ptr_factory_.GetWeakPtr()));

  nsnotification_registration_ =
      [[PrerenderBrowserAgentMemoryWarningObservation alloc]
          initWithClosure:base::BindRepeating(
                              &PrerenderBrowserAgent::CancelPrerenderInternal,
                              weak_ptr_factory_.GetWeakPtr(),
                              PrerenderFinalStatus::kMemoryLimitExceeded)];
}

PrerenderBrowserAgent::~PrerenderBrowserAgent() {
  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
  CancelPrerender();
}

void PrerenderBrowserAgent::SetDelegate(
    id<PrerenderBrowserAgentDelegate> delegate) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  delegate_ = delegate;
}

void PrerenderBrowserAgent::StartPrerender(const GURL& url,
                                           const web::Referrer& referrer,
                                           ui::PageTransition transition,
                                           PrerenderPolicy policy) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // If `url` is already pre-rendered, or a request to pre-render `url` is
  // already scheduled, then there is nothing to do. Return immediately.
  if (IsPrerenderdOrScheduled(url)) {
    return;
  }

  // If there is no active WebState or the url cannot be pre-rendered, then
  // cancel any prerendering and ignore the request.
  web::WebState* web_state = browser_->GetWebStateList()->GetActiveWebState();
  if (!web_state || !CanPrerenderUrl(url) || !Enabled()) {
    CancelPrerender();
    return;
  }

  scheduled_request_ = std::make_unique<Request<base::WeakPtr<web::WebState>>>(
      web_state->GetWeakPtr(), RequestInfos(url, referrer, transition));
  timer_.Start(FROM_HERE, DelayForPolicy(policy),
               base::BindOnce(&PrerenderBrowserAgent::StartPendingRequest,
                              weak_ptr_factory_.GetWeakPtr()));
}

bool PrerenderBrowserAgent::ValidatePrerender(const GURL& url,
                                              ui::PageTransition transition) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Ensure that the pre-render is cancelled when this method complete.
  base::ScopedClosureRunner cancel_prerender_on_cleanup(base::BindOnce(
      &PrerenderBrowserAgent::CancelPrerender, weak_ptr_factory_.GetWeakPtr()));

  if (!prerender_request_ || prerender_request_->infos().url() != url) {
    return false;
  }

  WebStateList* web_state_list = browser_->GetWebStateList();
  const int active_index = web_state_list->active_index();
  if (active_index == WebStateList::kInvalidIndex) {
    return false;
  }

  web::WebState* active_web_state = web_state_list->GetWebStateAt(active_index);
  CHECK(active_web_state);

  std::unique_ptr<web::WebState> new_web_state =
      ReleasePrerender(PrerenderFinalStatus::kUsed);
  if (!new_web_state) {
    return false;
  }

  // Due to some security workarounds inside //ios/web, sometimes a restored
  // WebState may mark new navigations as renderer initiated instead of browser
  // initiated. As a result "visible url" of the preloaded WebState will be the
  // "last committed url" and not "url typed by the user". As there navigations
  // are uncommitted, and make the omnibox (or NTP) look stange, drop them.
  // See crbug.com/1020497 for the strange UI, and crbug.com/1010765 for the
  // triggering security fixes.
  if (active_web_state->GetVisibleURL() == new_web_state->GetVisibleURL()) {
    return false;
  }

  // Remove the PrerenderTabHelper as the WebState will become a real tab.
  PrerenderTabHelper::RemoveFromWebState(new_web_state.get());

  // The WebState will be converted to a proper tab. Record navigations that
  // happened during pre-rendering to the HistoryService.
  if (HistoryTabHelper* tab_helper =
          HistoryTabHelper::FromWebState(new_web_state.get())) {
    tab_helper->SetDelayHistoryServiceNotification(false);
  }

  {
    base::AutoReset<bool> scoped_reset(&loading_prerender_, true);
    web_state_list->ReplaceWebStateAt(active_index, std::move(new_web_state));
    active_web_state = web_state_list->GetWebStateAt(active_index);
  }

  if (PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) ||
      PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_GENERATED)) {
    LoadTimingTabHelper::FromWebState(active_web_state)
        ->DidPromotePrerenderTab();
  }

  // There is no need to call CancelPrerender(), clear the ScopedClosureRunner.
  std::ignore = cancel_prerender_on_cleanup.Release();
  return true;
}

bool PrerenderBrowserAgent::IsInsertingPrerender() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return loading_prerender_;
}

void PrerenderBrowserAgent::CancelPrerender() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CancelPrerenderInternal(PrerenderFinalStatus::kCancelled);
}

bool PrerenderBrowserAgent::Enabled() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  ProfileIOS* profile = browser_->GetProfile();
  if (ios::device_util::IsSingleCoreDevice() ||
      !ios::device_util::RamIsAtLeast512Mb() ||
      net::NetworkChangeNotifier::IsOffline() ||
      supervised_user::IsSubjectToParentalControls(profile)) {
    return false;
  }

  switch (NetworkPredictionSettingFromPrefs(profile->GetPrefs())) {
    case prerender_prefs::NetworkPredictionSetting::kDisabled:
      return false;

    case prerender_prefs::NetworkPredictionSetting::kEnabledWifiOnly:
      return !net::NetworkChangeNotifier::IsConnectionCellular(
          net::NetworkChangeNotifier::GetConnectionType());

    case prerender_prefs::NetworkPredictionSetting::kEnabledWifiAndCellular:
      return true;
  }
}

bool PrerenderBrowserAgent::IsPrerenderdOrScheduled(const GURL& url) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (scheduled_request_) {
    if (scheduled_request_->infos().url() == url) {
      return true;
    }
  }

  if (prerender_request_) {
    if (prerender_request_->infos().url() == url) {
      return true;
    }
  }

  return false;
}

void PrerenderBrowserAgent::StartPendingRequest() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(scheduled_request_);
  CHECK(CanPrerenderUrl(scheduled_request_->infos().url()));
  auto request = std::exchange(scheduled_request_, nullptr);
  CancelPrerenderInternal(PrerenderFinalStatus::kCancelled);

  // If the WebState has been destroyed since the request was scheduled, there
  // is nothing to do (as we just destroyed the previous pre-rendering if any).
  web::WebState* active_web_state = request->web_state();
  if (!active_web_state) {
    return;
  }

  // To avoid losing the navigation history when the user navigates to the
  // pre-rendered tab, clone the tab that will be replaced, and start the
  // pre-rendered navigation in the new tab.
  CHECK(!prerender_request_);
  prerender_request_ =
      std::make_unique<Request<std::unique_ptr<web::WebState>>>(
          active_web_state->Clone(), request->infos());
  web::WebState* web_state = prerender_request_->web_state();

  // Create the delegate, observer and policy decider before any tab helper
  // to ensure they will be the first notified of the respective changes to
  // the WebState and can act before any tab helper can have any side-effect
  // (e.g. AppLauncherTabHelper launching an external application).
  web_state_delegate_ = std::make_unique<Delegate>(web_state, this);
  web_state_observer_ = std::make_unique<Observer>(web_state, this);
  policy_decider_ = std::make_unique<PolicyDecider>(web_state, this);

  // Create the PrerenderTabHelper before any other TabHelpers to ensure
  // they all correctly see this WebState as used for pre-rendering.
  PrerenderTabHelper::CreateForWebState(web_state, this);

  AttachTabHelpers(web_state, TabHelperFilter::kPrerender);
  crash_report_helper::MonitorURLsForPreloadWebState(web_state);

  ProfileIOS* profile = browser_->GetProfile();
  if (AccountConsistencyService* service =
          ios::AccountConsistencyServiceFactory::GetForProfile(profile)) {
    if (!manage_accounts_delegate_) {
      manage_accounts_delegate_ =
          std::make_unique<ManageAccountsDelegate>(this);
    }
    service->SetWebStateHandler(web_state, manage_accounts_delegate_.get());
  }

  if (HistoryTabHelper* tab_helper =
          HistoryTabHelper::FromWebState(web_state)) {
    tab_helper->SetDelayHistoryServiceNotification(true);
  }

  web_state->SetWebUsageEnabled(true);
  web_state->SetKeepRenderProcessAlive(true);

  // LoadIfNecessary() is needed because the view is not created (but needed)
  // when loading the page. TODO(crbug.com/41309809): remove this call.
  web::NavigationManager* manager = web_state->GetNavigationManager();
  manager->LoadURLWithParams(prerender_request_->infos().load_params());
  manager->LoadIfNecessary();
}

void PrerenderBrowserAgent::ScheduleCancelPrerender() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CancelScheduledRequest();
  timer_.Start(FROM_HERE, base::TimeDelta(),
               base::BindOnce(&PrerenderBrowserAgent::CancelPrerender,
                              weak_ptr_factory_.GetWeakPtr()));
}

void PrerenderBrowserAgent::CancelScheduledRequest() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (scheduled_request_) {
    scheduled_request_.reset();
    timer_.Stop();
  }
}

void PrerenderBrowserAgent::CancelPrerenderInternal(
    PrerenderFinalStatus reason) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CancelScheduledRequest();
  DestroyPrerender(reason);
}

void PrerenderBrowserAgent::DestroyPrerender(PrerenderFinalStatus reason) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!prerender_request_) {
    return;
  }

  // Dropping the std::unique_ptr<...> causes the destruction of the WebState
  // object used for pre-render.
  std::ignore = ReleasePrerender(reason);
}

std::unique_ptr<web::WebState> PrerenderBrowserAgent::ReleasePrerender(
    PrerenderFinalStatus reason) {
  CHECK(prerender_request_);
  CHECK(web_state_observer_);

  base::UmaHistogramEnumeration(kPrerenderFinalStatusHistogramName, reason);
  if (reason == PrerenderFinalStatus::kUsed) {
    base::UmaHistogramBoolean(kPrerenderLoadComplete,
                              web_state_observer_->load_completed());
    base::UmaHistogramTimes(kPrerenderPrerenderTimeSaved,
                            web_state_observer_->time_saved());
  }

  auto request = std::exchange(prerender_request_, nullptr);
  std::unique_ptr<web::WebState> web_state = request->Release();

  crash_report_helper::StopMonitoringURLsForPreloadWebState(web_state.get());
  if (AccountConsistencyService* service =
          ios::AccountConsistencyServiceFactory::GetForProfile(
              browser_->GetProfile())) {
    service->RemoveWebStateHandler(web_state.get());
  }

  policy_decider_.reset();
  web_state_observer_.reset();
  web_state_delegate_.reset();

  return web_state;
}

void PrerenderBrowserAgent::OnNetworkPredictionSettingChanged() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!Enabled()) {
    CancelPrerender();
  }
}

void PrerenderBrowserAgent::OnNetworkChanged(
    net::NetworkChangeNotifier::ConnectionType type) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!Enabled()) {
    CancelPrerender();
  }
}
