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

#include "components/safe_browsing/content/browser/browser_url_loader_throttle.h"

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "components/safe_browsing/buildflags.h"
#include "components/safe_browsing/content/browser/async_check_tracker.h"
#include "components/safe_browsing/content/browser/safe_browsing_navigation_observer.h"
#include "components/safe_browsing/core/browser/db/v5_get_hash_protocol_manager.h"
#include "components/safe_browsing/core/browser/hashprefix_realtime/hash_realtime_service.h"
#include "components/safe_browsing/core/browser/realtime/url_lookup_service_base.h"
#include "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
#include "components/safe_browsing/core/browser/url_checker_delegate.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/utils.h"
#include "components/safe_browsing/core/common/web_ui_constants.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_flags.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"

namespace {

constexpr char kFullURLLookup[] = "FullUrlLookup";

constexpr char kFromCacheUmaSuffix[] = ".FromCache";
constexpr char kFromNetworkUmaSuffix[] = ".FromNetwork";

void LogTotalDelay2Metrics(const std::string& url_check_type,
                           base::TimeDelta total_delay) {
  base::UmaHistogramTimes(
      base::StrCat(
          {"SafeBrowsing.BrowserThrottle.TotalDelay2", url_check_type}),
      total_delay);
}

void LogTotalDelay2MetricsWithResponseType(bool is_response_from_cache,
                                           base::TimeDelta total_delay) {
  base::UmaHistogramTimes(
      base::StrCat({"SafeBrowsing.BrowserThrottle.TotalDelay2",
                    is_response_from_cache ? kFromCacheUmaSuffix
                                           : kFromNetworkUmaSuffix}),
      total_delay);
}

// Returns true if the URL is known to be safe. We also require that this URL
// never redirects to a potentially unsafe URL, because the redirected URLs are
// also skipped if this function returns true.
bool KnownSafeUrl(const GURL& url) {
  return url.SchemeIs(content::kChromeUIScheme) &&
         !safe_browsing::IsSafeBrowsingWebUIUrl(url);
}

}  // namespace

namespace safe_browsing {

BrowserURLLoaderThrottle::SkipCheckChecker::SkipCheckChecker(
    UrlCheckerHolder::GetDelegateCallback delegate_getter,
    content::FrameTreeNodeId frame_tree_node_id)
    : delegate_getter_(std::move(delegate_getter)),
      frame_tree_node_id_(frame_tree_node_id) {}

BrowserURLLoaderThrottle::SkipCheckChecker::~SkipCheckChecker() = default;

void BrowserURLLoaderThrottle::SkipCheckChecker::CheckOriginalUrl(
    OnCompleteCheckCallback callback,
    const GURL& url,
    bool originated_from_service_worker) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  scoped_refptr<UrlCheckerDelegate> url_checker_delegate =
      std::move(delegate_getter_).Run();
  should_skip_checks_ =
      !url_checker_delegate ||
      url_checker_delegate->ShouldSkipRequestCheck(
          url, frame_tree_node_id_.value(),
          /*render_process_id=*/content::ChildProcessHost::kInvalidUniqueID,
          /*render_frame_token=*/std::nullopt, originated_from_service_worker);
  std::move(callback).Run(should_skip_checks_);
}

void BrowserURLLoaderThrottle::SkipCheckChecker::CheckRedirectUrl(
    OnCompleteCheckCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  std::move(callback).Run(should_skip_checks_);
}

// static
std::unique_ptr<BrowserURLLoaderThrottle> BrowserURLLoaderThrottle::Create(
    UrlCheckerHolder::GetDelegateCallback delegate_getter,
    const base::RepeatingCallback<content::WebContents*()>& web_contents_getter,
    content::FrameTreeNodeId frame_tree_node_id,
    std::optional<int64_t> navigation_id,
    base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service,
    base::WeakPtr<HashRealTimeService> hash_realtime_service,
    hash_realtime_utils::HashRealTimeSelection hash_realtime_selection,
    base::WeakPtr<AsyncCheckTracker> async_check_tracker,
    std::optional<internal::ReferringAppInfo> referring_app_info,
    base::WeakPtr<safe_browsing::V5GetHashProtocolManager>
        v5_get_hash_protocol_manager) {
  return base::WrapUnique<BrowserURLLoaderThrottle>(
      new BrowserURLLoaderThrottle(
          std::move(delegate_getter), web_contents_getter, frame_tree_node_id,
          navigation_id, url_lookup_service, hash_realtime_service,
          hash_realtime_selection, async_check_tracker,
          std::move(referring_app_info), v5_get_hash_protocol_manager));
}

BrowserURLLoaderThrottle::BrowserURLLoaderThrottle(
    UrlCheckerHolder::GetDelegateCallback delegate_getter,
    const base::RepeatingCallback<content::WebContents*()>& web_contents_getter,
    content::FrameTreeNodeId frame_tree_node_id,
    std::optional<int64_t> navigation_id,
    base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service,
    base::WeakPtr<HashRealTimeService> hash_realtime_service,
    hash_realtime_utils::HashRealTimeSelection hash_realtime_selection,
    base::WeakPtr<AsyncCheckTracker> async_check_tracker,
    std::optional<internal::ReferringAppInfo> referring_app_info,
    base::WeakPtr<safe_browsing::V5GetHashProtocolManager>
        v5_get_hash_protocol_manager)
    : async_check_tracker_(async_check_tracker),
      url_lookup_service_(url_lookup_service),
      hash_realtime_service_(hash_realtime_service),
      hash_realtime_selection_(hash_realtime_selection),
      frame_tree_node_id_(frame_tree_node_id),
      navigation_id_(navigation_id),
      delegate_getter_(delegate_getter),
      web_contents_getter_(web_contents_getter),
      referring_app_info_(referring_app_info),
      v5_get_hash_protocol_manager_(v5_get_hash_protocol_manager) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // Decide whether to do real time URL lookups or not.
  url_real_time_lookup_enabled_ =
      url_lookup_service_ ? url_lookup_service_->CanPerformFullURLLookup()
                          : false;
  url_lookup_service_metric_suffix_ =
      url_real_time_lookup_enabled_ ? url_lookup_service_->GetMetricSuffix()
                                    : kNoRealTimeURLLookupService;
  content::WebContents* web_contents = web_contents_getter_.Run();
  tab_id_ = web_contents ? sessions::SessionTabHelper::IdForTab(web_contents)
                         : SessionID::InvalidValue();
  skip_check_checker_ =
      std::make_unique<SkipCheckChecker>(delegate_getter_, frame_tree_node_id);
}

BrowserURLLoaderThrottle::~BrowserURLLoaderThrottle() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (deferred_) {
    TRACE_EVENT_END("safe_browsing", /* Deferred */
                    perfetto::NamedTrack::FromPointer(
                        "safe_browsing::BrowserURLLoaderThrottle", this));
  }
  if (was_async_faster_than_sync_.has_value()) {
    base::UmaHistogramBoolean(
        "SafeBrowsing.BrowserThrottle.IsAsyncCheckFasterThanSyncCheck",
        was_async_faster_than_sync_.value());
  }
  DeleteUrlChecker();
}

void BrowserURLLoaderThrottle::WillStartRequest(
    network::ResourceRequest* request,
    bool* defer) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK_EQ(0u, pending_sync_checks_);
  DCHECK_EQ(0u, pending_async_checks_);
  DCHECK(!blocked_);

  current_url_ = request->url;

  base::UmaHistogramEnumeration(
      "SafeBrowsing.BrowserThrottle.RequestDestination", request->destination);

  // Decision override used in enterprise mode to send safe urls for check
  // since admins can ban chrome:// pages
  bool should_override_known_safe_decision =
      url_real_time_lookup_enabled_ &&
      url_lookup_service_->ShouldOverrideKnownSafeUrlDecision(request->url);
  if (KnownSafeUrl(request->url) && !should_override_known_safe_decision) {
    skip_checks_ = true;
    return;
  }

  if (request->destination != network::mojom::RequestDestination::kDocument) {
    VLOG(2) << __func__ << " : Skipping: " << request->url << " : "
            << request->destination;
    base::UmaHistogramEnumeration(
        "SafeBrowsing.BrowserThrottle.RequestDestination.Skipped",
        request->destination);
    skip_checks_ = true;
    return;
  }

  base::UmaHistogramEnumeration(
      "SafeBrowsing.BrowserThrottle.RequestDestination.Checked",
      request->destination);

  // Decide whether safe browsing database can be checked.
  // If url_lookup_service_ is null, safe browsing database should be checked by
  // default.
  bool can_check_db = url_lookup_service_
                          ? url_lookup_service_->CanCheckSafeBrowsingDb()
                          : true;
  bool can_check_high_confidence_allowlist =
      url_lookup_service_
          ? url_lookup_service_->CanCheckSafeBrowsingHighConfidenceAllowlist()
          : true;

  if (async_check_tracker_ && navigation_id_.has_value() &&
      !(request->load_flags & net::LOAD_PREFETCH)) {
    CHECK(can_check_db);
    CHECK(url_real_time_lookup_enabled_ ||
          hash_realtime_selection_ !=
              hash_realtime_utils::HashRealTimeSelection::kNone);
    // If async check is enabled, sync_sb_checker only performs local database
    // check.
    sync_sb_checker_ = std::make_unique<UrlCheckerHolder>(
        delegate_getter_, frame_tree_node_id_, navigation_id_,
        web_contents_getter_,
        /*complete_callback=*/
        base::BindRepeating(&BrowserURLLoaderThrottle::OnCompleteSyncCheck,
                            weak_factory_.GetWeakPtr()),
        /*url_real_time_lookup_enabled=*/false, can_check_db,
        /*can_check_high_confidence_allowlist=*/true,
        /*url_lookup_service_metric_suffix=*/kNoRealTimeURLLookupService,
        /*url_lookup_service=*/nullptr,
        /*hash_realtime_service=*/nullptr,
        /*hash_realtime_selection=*/
        hash_realtime_utils::HashRealTimeSelection::kNone,
        /*is_async_check=*/false,
        /*check_allowlist_before_hash_database=*/
        async_check_tracker_->should_sync_checker_check_allowlist(),
        /*tab_id=*/SessionID::InvalidValue(), referring_app_info_,
        v5_get_hash_protocol_manager_);
    async_sb_checker_ = std::make_unique<UrlCheckerHolder>(
        delegate_getter_, frame_tree_node_id_, navigation_id_,
        web_contents_getter_,
        /*complete_callback=*/
        base::BindRepeating(&BrowserURLLoaderThrottle::OnCompleteAsyncCheck,
                            weak_factory_.GetWeakPtr()),
        url_real_time_lookup_enabled_, can_check_db,
        can_check_high_confidence_allowlist, url_lookup_service_metric_suffix_,
        url_lookup_service_, hash_realtime_service_, hash_realtime_selection_,
        /*is_async_check=*/true, /*check_allowlist_before_hash_database=*/false,
        tab_id_, referring_app_info_, v5_get_hash_protocol_manager_);
    if (on_sync_sb_checker_created_callback_for_testing_) {
      std::move(on_sync_sb_checker_created_callback_for_testing_).Run();
    }
    if (on_async_sb_checker_created_callback_for_testing_) {
      std::move(on_async_sb_checker_created_callback_for_testing_).Run();
    }
  } else {
    sync_sb_checker_ = std::make_unique<UrlCheckerHolder>(
        delegate_getter_, frame_tree_node_id_, navigation_id_,
        web_contents_getter_,
        /*complete_callback=*/
        base::BindRepeating(&BrowserURLLoaderThrottle::OnCompleteSyncCheck,
                            weak_factory_.GetWeakPtr()),
        url_real_time_lookup_enabled_, can_check_db,
        can_check_high_confidence_allowlist, url_lookup_service_metric_suffix_,
        url_lookup_service_, hash_realtime_service_, hash_realtime_selection_,
        /*is_async_check=*/false,
        /*check_allowlist_before_hash_database=*/false, tab_id_,
        referring_app_info_, v5_get_hash_protocol_manager_);
    if (on_sync_sb_checker_created_callback_for_testing_) {
      std::move(on_sync_sb_checker_created_callback_for_testing_).Run();
    }
  }

  pending_sync_checks_++;
  if (async_sb_checker_) {
    pending_async_checks_++;
  }
  was_async_faster_than_sync_ = std::nullopt;
  start_request_time_ = base::TimeTicks::Now();
  is_start_request_called_ = true;

  skip_check_checker_->CheckOriginalUrl(
      base::BindOnce(
          &BrowserURLLoaderThrottle::OnSkipCheckCompleteOnOriginalUrl,
          weak_factory_.GetWeakPtr(), request->headers, request->load_flags,
          request->has_user_gesture, request->url, request->method),
      request->url, request->originated_from_service_worker);
}

void BrowserURLLoaderThrottle::OnSkipCheckCompleteOnOriginalUrl(
    const net::HttpRequestHeaders& headers,
    int load_flags,
    bool has_user_gesture,
    const GURL& url,
    const std::string& method,
    bool should_skip) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (should_skip) {
    SkipChecks();
    return;
  }

  UrlCheckerHolder::StartParams params(headers, load_flags, has_user_gesture,
                                       url, method);
  sync_sb_checker_->Start(params);
  if (async_sb_checker_) {
    async_sb_checker_->Start(params);
  }
}

void BrowserURLLoaderThrottle::WillRedirectRequest(
    net::RedirectInfo* redirect_info,
    const network::mojom::URLResponseHead& response_head,
    bool* defer,
    network::HttpRequestHeadersUpdateParams* headers_update_params) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (blocked_) {
    // OnCheckUrlResult() has set |blocked_| to true and called
    // |delegate_->CancelWithError|, but this method is called before the
    // request is actually cancelled. In that case, simply defer the request.
    *defer = true;
    return;
  }

  if (skip_checks_) {
    return;
  }

  // Make IP addresses from redirects available for later real-time URL checks.
  if (!response_head.remote_endpoint.address().empty()) {
    content::WebContents* web_contents = web_contents_getter_.Run();
    if (web_contents) {
      SafeBrowsingNavigationObserver* navigation_observer =
          SafeBrowsingNavigationObserver::FromWebContents(web_contents);
      if (navigation_observer) {
        navigation_observer->RecordHostToIpMapping(
            current_url_.GetHost(),
            response_head.remote_endpoint.ToStringWithoutPort());
      }
    }
  }
  current_url_ = redirect_info->new_url;

  pending_sync_checks_++;
  if (async_sb_checker_) {
    pending_async_checks_++;
  }
  was_async_faster_than_sync_ = std::nullopt;

  // The check to |skip_check_checker| cannot be skipped because
  // WillRedirectRequest may be called while |skip_check_checker| is still in
  // progress.
  skip_check_checker_->CheckRedirectUrl(base::BindOnce(
      &BrowserURLLoaderThrottle::OnSkipCheckCompleteOnRedirectUrl,
      weak_factory_.GetWeakPtr(), redirect_info->new_url,
      redirect_info->new_method));
}

void BrowserURLLoaderThrottle::OnSkipCheckCompleteOnRedirectUrl(
    const GURL& url,
    const std::string& method,
    bool should_skip) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (should_skip) {
    SkipChecks();
    return;
  }

  if (blocked_) {
    // The original URL may already be blocked while skip check checker is
    // performing the check. In that case, the SB checkers were already deleted.
    return;
  }

  sync_sb_checker_->CheckUrl(url, method);
  if (async_sb_checker_) {
    async_sb_checker_->CheckUrl(url, method);
  }
}

void BrowserURLLoaderThrottle::WillProcessResponse(
    const GURL& response_url,
    network::mojom::URLResponseHead* response_head,
    bool* defer) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (blocked_) {
    // OnCompleteCheck() has set |blocked_| to true and called
    // |delegate_->CancelWithError|, but this method is called before the
    // request is actually cancelled. In that case, simply defer the request.
    *defer = true;
    return;
  }

  bool sync_check_completed = (pending_sync_checks_ == 0);
  base::UmaHistogramBoolean(
      "SafeBrowsing.BrowserThrottle.IsCheckCompletedOnProcessResponse",
      sync_check_completed);
  is_response_from_cache_ =
      response_head->was_fetched_via_cache && !response_head->network_accessed;
  if (is_start_request_called_) {
    base::TimeDelta interval = base::TimeTicks::Now() - start_request_time_;
    base::UmaHistogramTimes(
        "SafeBrowsing.BrowserThrottle.IntervalBetweenStartAndProcess",
        interval);
    base::UmaHistogramTimes(
        base::StrCat(
            {"SafeBrowsing.BrowserThrottle.IntervalBetweenStartAndProcess",
             is_response_from_cache_ ? kFromCacheUmaSuffix
                                     : kFromNetworkUmaSuffix}),
        interval);
    if (sync_check_completed) {
      LogTotalDelay2MetricsWithResponseType(is_response_from_cache_,
                                            base::TimeDelta());
    }
    is_start_request_called_ = false;
  }

  if (sync_check_completed) {
    MaybeTransferAsyncChecker();
    return;
  }

  DCHECK(!deferred_);
  deferred_ = true;
  defer_start_time_ = base::TimeTicks::Now();
  *defer = true;
  TRACE_EVENT_BEGIN("safe_browsing", "Deferred",
                    perfetto::NamedTrack::FromPointer(
                        "safe_browsing::BrowserURLLoaderThrottle", this));
}

const char* BrowserURLLoaderThrottle::NameForLoggingWillProcessResponse() {
  return "SafeBrowsingBrowserThrottle";
}

UrlCheckerHolder* BrowserURLLoaderThrottle::GetSyncSBCheckerForTesting() {
  return sync_sb_checker_.get();
}

UrlCheckerHolder* BrowserURLLoaderThrottle::GetAsyncSBCheckerForTesting() {
  return async_sb_checker_.get();
}

void BrowserURLLoaderThrottle::SetOnSyncSBCheckerCreatedCallbackForTesting(
    base::OnceClosure callback) {
  on_sync_sb_checker_created_callback_for_testing_ = std::move(callback);
}

void BrowserURLLoaderThrottle::SetOnAsyncSBCheckerCreatedCallbackForTesting(
    base::OnceClosure callback) {
  on_async_sb_checker_created_callback_for_testing_ = std::move(callback);
}

void BrowserURLLoaderThrottle::OnCompleteSyncCheck(
    UrlCheckerHolder::OnCompleteCheckResult result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(url_real_time_lookup_enabled_ ||
         result.performed_check !=
             SafeBrowsingUrlCheckerImpl::PerformedCheck::kUrlRealTimeCheck);

  // |blocked| may already be set by |async_sb_checker_|.
  if (blocked_) {
    return;
  }

  DCHECK_LT(0u, pending_sync_checks_);
  pending_sync_checks_--;
  if (async_sb_checker_ && pending_sync_checks_ == 0 &&
      !was_async_faster_than_sync_.has_value()) {
    was_async_faster_than_sync_ = false;
  }

  // If the resource load is going to finish (either being cancelled or
  // resumed), record the total delay.
  if (!result.proceed || pending_sync_checks_ == 0) {
    // If the resource load is currently deferred, there is a delay.
    if (deferred_) {
      total_delay_ = base::TimeTicks::Now() - defer_start_time_;
      LogTotalDelay2MetricsWithResponseType(is_response_from_cache_,
                                            total_delay_);
    }
    LogTotalDelay2Metrics(GetUrlCheckTypeForLogging(result.performed_check),
                          total_delay_);
  }

  if (result.proceed) {
    if (pending_sync_checks_ == 0 && deferred_) {
      deferred_ = false;
      TRACE_EVENT_END("safe_browsing", /* Deferred */
                      perfetto::NamedTrack::FromPointer(
                          "safe_browsing::BrowserURLLoaderThrottle", this));
      delegate_->Resume();
      MaybeTransferAsyncChecker();
    }
  } else {
    BlockUrlLoader(result.showed_interstitial);
  }
}

void BrowserURLLoaderThrottle::OnCompleteAsyncCheck(
    UrlCheckerHolder::OnCompleteCheckResult result) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // |blocked| may already be set by |sync_sb_checker_|.
  if (blocked_) {
    return;
  }

  DCHECK_LT(0u, pending_async_checks_);
  pending_async_checks_--;
  if (pending_async_checks_ == 0 && !was_async_faster_than_sync_.has_value()) {
    was_async_faster_than_sync_ = true;
  }

  if (!result.proceed) {
    BlockUrlLoader(result.showed_interstitial);
  }
  // There is no need to set |deferred_| for async check because it never defers
  // URL loader.
}

void BrowserURLLoaderThrottle::BlockUrlLoader(bool showed_interstitial) {
  blocked_ = true;

  DeleteUrlChecker();
  // If we didn't show an interstitial, we cancel with ERR_ABORTED to not show
  // an error page either.
  delegate_->CancelWithError(
      showed_interstitial ? kNetErrorCodeForSafeBrowsing : net::ERR_ABORTED,
      kCustomCancelReasonForURLLoader);
}

std::string BrowserURLLoaderThrottle::GetUrlCheckTypeForLogging(
    SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check) {
  switch (performed_check) {
    case SafeBrowsingUrlCheckerImpl::PerformedCheck::kUrlRealTimeCheck:
      return base::StrCat({url_lookup_service_metric_suffix_, kFullURLLookup});
    case SafeBrowsingUrlCheckerImpl::PerformedCheck::kHashDatabaseCheck:
      return ".HashPrefixDatabaseCheck";
    case SafeBrowsingUrlCheckerImpl::PerformedCheck::kCheckSkipped:
      return ".SkippedCheck";
    case SafeBrowsingUrlCheckerImpl::PerformedCheck::kHashRealTimeCheck:
      return ".HashPrefixRealTimeCheck";
    case SafeBrowsingUrlCheckerImpl::PerformedCheck::kUnknown:
      NOTREACHED();
  }
}

void BrowserURLLoaderThrottle::SkipChecks() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // Future checks for redirects will be skipped.
  skip_checks_ = true;

  pending_sync_checks_--;
  if (async_sb_checker_) {
    pending_async_checks_--;
    // Don't set |was_async_faster_than_sync_| if the counter reaches 0,
    // because the checks being skipped is considered a tie between sync and
    // async checks.
  }
  if (pending_sync_checks_ == 0 && deferred_) {
    delegate_->Resume();
  }
}

void BrowserURLLoaderThrottle::DeleteUrlChecker() {
  pending_sync_checks_ = 0;
  pending_async_checks_ = 0;
  sync_sb_checker_.reset();
  async_sb_checker_.reset();
  skip_check_checker_.reset();
}

void BrowserURLLoaderThrottle::MaybeTransferAsyncChecker() {
  // If the sync check has completed but the async check has not, move the async
  // check to AsyncCheckTracker.
  DCHECK_EQ(pending_sync_checks_, 0u);
  if (async_sb_checker_) {
    base::UmaHistogramBoolean(
        "SafeBrowsing.BrowserThrottle.IsAsyncCheckerTransferred",
        pending_async_checks_ > 0);
  }
  if (pending_async_checks_ > 0) {
    bool is_async_check_tracker_alive = !!async_check_tracker_;
    base::UmaHistogramBoolean(
        "SafeBrowsing.BrowserThrottle.IsAsyncCheckTrackerAliveOnTransfer",
        is_async_check_tracker_alive);
    if (is_async_check_tracker_alive) {
      async_check_tracker_->TransferUrlChecker(std::move(async_sb_checker_));
    }
  }
}

}  // namespace safe_browsing
