910e62b5创建于 1月15日历史提交
// 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 "content/browser/preloading/preconnect/preconnect_manager_impl.h"

#include <utility>

#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "base/types/optional_util.h"
#include "content/browser/preloading/proxy_lookup_client_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/preconnect_request.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/network/public/mojom/connection_change_observer_client.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"

namespace content {

const bool kAllowCredentialsOnPreconnectByDefault = true;

std::unique_ptr<PreconnectManager> PreconnectManager::Create(
    base::WeakPtr<PreconnectManager::Delegate> delegate,
    content::BrowserContext* browser_context) {
  return std::make_unique<PreconnectManagerImpl>(std::move(delegate),
                                                 browser_context);
}

PreconnectedRequestStats::PreconnectedRequestStats(const url::Origin& origin,
                                                   bool was_preconnected)
    : origin(origin), was_preconnected(was_preconnected) {}

PreconnectedRequestStats::PreconnectedRequestStats(
    const PreconnectedRequestStats& other) = default;
PreconnectedRequestStats::~PreconnectedRequestStats() = default;

PreconnectStats::PreconnectStats(const GURL& url)
    : url(url), start_time(base::TimeTicks::Now()) {}
PreconnectStats::~PreconnectStats() = default;

PreresolveInfo::PreresolveInfo(const GURL& url, size_t count)
    : url(url),
      queued_count(count),
      inflight_count(0),
      was_canceled(false),
      stats(std::make_unique<PreconnectStats>(url)) {}

PreresolveInfo::~PreresolveInfo() = default;

PreresolveJob::PreresolveJob(
    const GURL& url,
    int num_sockets,
    bool allow_credentials,
    net::NetworkAnonymizationKey network_anonymization_key,
    net::NetworkTrafficAnnotationTag traffic_annotation_tag,
    std::optional<content::StoragePartitionConfig> storage_partition_config,
    std::optional<net::ConnectionKeepAliveConfig> keepalive_config,
    mojo::PendingRemote<network::mojom::ConnectionChangeObserverClient>
        connection_change_observer_client,
    PreresolveInfo* info)
    : url(url),
      num_sockets(num_sockets),
      allow_credentials(allow_credentials),
      network_anonymization_key(std::move(network_anonymization_key)),
      traffic_annotation_tag(std::move(traffic_annotation_tag)),
      storage_partition_config(std::move(storage_partition_config)),
      keepalive_config(std::move(keepalive_config)),
      connection_change_observer_client(
          std::move(connection_change_observer_client)),
      info(info),
      creation_time(base::TimeTicks::Now()) {
  CHECK_GE(num_sockets, 0);

  CHECK(!this->network_anonymization_key.IsEmpty() ||
        !net::NetworkAnonymizationKey::IsPartitioningEnabled());
}

PreresolveJob::PreresolveJob(
    content::PreconnectRequest preconnect_request,
    PreresolveInfo* info,
    net::NetworkTrafficAnnotationTag traffic_annotation_tag)
    : PreresolveJob(preconnect_request.origin.GetURL(),
                    preconnect_request.num_sockets,
                    preconnect_request.allow_credentials,
                    std::move(preconnect_request.network_anonymization_key),
                    traffic_annotation_tag,
                    /*storage_partition_config=*/std::nullopt,
                    std::nullopt,
                    mojo::NullRemote(),
                    info) {}

PreresolveJob::PreresolveJob(PreresolveJob&& other) = default;
PreresolveJob::~PreresolveJob() = default;

PreconnectManagerImpl::PreconnectManagerImpl(
    base::WeakPtr<Delegate> delegate,
    content::BrowserContext* browser_context)
    : delegate_(std::move(delegate)),
      browser_context_(browser_context),
      inflight_preresolves_count_(0) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(browser_context_);
}

PreconnectManagerImpl::~PreconnectManagerImpl() = default;

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

void PreconnectManagerImpl::SetNetworkContextForTesting(
    network::mojom::NetworkContext* network_context) {
  network_context_ = network_context;
}

void PreconnectManagerImpl::SetObserverForTesting(Observer* observer) {
  observer_ = observer;
}

void PreconnectManagerImpl::Start(
    const GURL& url,
    std::vector<content::PreconnectRequest> requests,
    net::NetworkTrafficAnnotationTag traffic_annotation) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!delegate_ || !delegate_->IsPreconnectEnabled()) {
    return;
  }
  if (!url.SchemeIsHTTPOrHTTPS()) {
    return;
  }
  PreresolveInfo* info;
  if (preresolve_info_.find(url) == preresolve_info_.end()) {
    auto iterator_and_whether_inserted = preresolve_info_.emplace(
        url, std::make_unique<PreresolveInfo>(url, requests.size()));
    info = iterator_and_whether_inserted.first->second.get();
  } else {
    info = preresolve_info_.find(url)->second.get();
    info->queued_count += requests.size();
  }

  for (auto& request : requests) {
    PreresolveJobId job_id =
        preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
            std::move(request), info, traffic_annotation));
    queued_jobs_.push_back(job_id);
  }

  TryToLaunchPreresolveJobs();
}

void PreconnectManagerImpl::StartPreresolveHost(
    const GURL& url,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    net::NetworkTrafficAnnotationTag traffic_annotation,
    const content::StoragePartitionConfig* storage_partition_config) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!delegate_ || !delegate_->IsPreconnectEnabled()) {
    return;
  }
  if (!url.SchemeIsHTTPOrHTTPS()) {
    return;
  }
  PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
      url.DeprecatedGetOriginAsURL(), 0, kAllowCredentialsOnPreconnectByDefault,
      network_anonymization_key, traffic_annotation,
      base::OptionalFromPtr(storage_partition_config), std::nullopt,
      mojo::NullRemote(), nullptr));
  queued_jobs_.push_front(job_id);

  TryToLaunchPreresolveJobs();
}

void PreconnectManagerImpl::StartPreresolveHosts(
    const std::vector<GURL>& urls,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    net::NetworkTrafficAnnotationTag traffic_annotation,
    const content::StoragePartitionConfig* storage_partition_config) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!delegate_ || !delegate_->IsPreconnectEnabled()) {
    return;
  }
  // Push jobs in front of the queue due to higher priority.
  for (const GURL& url : base::Reversed(urls)) {
    if (!url.SchemeIsHTTPOrHTTPS()) {
      continue;
    }
    PreresolveJobId job_id =
        preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
            url.DeprecatedGetOriginAsURL(), 0,
            kAllowCredentialsOnPreconnectByDefault, network_anonymization_key,
            traffic_annotation, base::OptionalFromPtr(storage_partition_config),
            std::nullopt, mojo::NullRemote(), nullptr));
    queued_jobs_.push_front(job_id);
  }

  TryToLaunchPreresolveJobs();
}

void PreconnectManagerImpl::StartPreconnectUrl(
    const GURL& url,
    bool allow_credentials,
    net::NetworkAnonymizationKey network_anonymization_key,
    net::NetworkTrafficAnnotationTag traffic_annotation,
    const content::StoragePartitionConfig* storage_partition_config,
    std::optional<net::ConnectionKeepAliveConfig> keepalive_config,
    mojo::PendingRemote<network::mojom::ConnectionChangeObserverClient>
        connection_change_observer_client) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!delegate_ || !delegate_->IsPreconnectEnabled()) {
    return;
  }
  if (!url.SchemeIsHTTPOrHTTPS()) {
    return;
  }
  PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
      url.DeprecatedGetOriginAsURL(), 1, allow_credentials,
      std::move(network_anonymization_key), traffic_annotation,
      base::OptionalFromPtr(storage_partition_config),
      std::move(keepalive_config), std::move(connection_change_observer_client),
      nullptr));
  queued_jobs_.push_front(job_id);

  TryToLaunchPreresolveJobs();
}

void PreconnectManagerImpl::Stop(const GURL& url) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto it = preresolve_info_.find(url);
  if (it == preresolve_info_.end()) {
    return;
  }

  it->second->was_canceled = true;
}

void PreconnectManagerImpl::PreconnectUrl(
    const GURL& url,
    int num_sockets,
    bool allow_credentials,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    const net::NetworkTrafficAnnotationTag& traffic_annotation,
    const content::StoragePartitionConfig* storage_partition_config,
    std::optional<net::ConnectionKeepAliveConfig> keepalive_config,
    mojo::PendingRemote<network::mojom::ConnectionChangeObserverClient>
        connection_change_observer_client) const {
  DCHECK(url.DeprecatedGetOriginAsURL() == url);
  DCHECK(url.SchemeIsHTTPOrHTTPS());
  if (observer_) {
    observer_->OnPreconnectUrl(url, num_sockets, allow_credentials);
  }

  auto* network_context = GetNetworkContext(storage_partition_config);

  if (num_sockets > 1 &&
      base::FeatureList::IsEnabled(
          features::kLoadingPredictorLimitPreconnectSocketCount)) {
    // Adjust the number of socket here because LoadingPredictor is the only
    // call site that sets `num_sockets` to a non-one value.
    num_sockets = 1;
  }

  // TODO(crbug.com/406022435): pass the actual `keepalive_config` from the
  // caller.
  network_context->PreconnectSockets(
      num_sockets, url,
      allow_credentials ? network::mojom::CredentialsMode::kInclude
                        : network::mojom::CredentialsMode::kOmit,
      network_anonymization_key,
      net::MutableNetworkTrafficAnnotationTag(traffic_annotation),
      std::move(keepalive_config),
      std::move(connection_change_observer_client));
}

std::unique_ptr<ResolveHostClientImpl> PreconnectManagerImpl::PreresolveUrl(
    const GURL& url,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    const content::StoragePartitionConfig* storage_partition_config,
    ResolveHostCallback callback) const {
  DCHECK(url.DeprecatedGetOriginAsURL() == url);
  DCHECK(url.SchemeIsHTTPOrHTTPS());

  auto* network_context = GetNetworkContext(storage_partition_config);

  return std::make_unique<ResolveHostClientImpl>(
      url, network_anonymization_key, std::move(callback), network_context);
}

std::unique_ptr<ProxyLookupClientImpl> PreconnectManagerImpl::LookupProxyForUrl(
    const GURL& url,
    const net::NetworkAnonymizationKey& network_anonymization_key,
    const content::StoragePartitionConfig* storage_partition_config,
    ProxyLookupClientImpl::ProxyLookupCallback callback) const {
  DCHECK(url.DeprecatedGetOriginAsURL() == url);
  DCHECK(url.SchemeIsHTTPOrHTTPS());

  auto* network_context = GetNetworkContext(storage_partition_config);

  return std::make_unique<ProxyLookupClientImpl>(
      url, network_anonymization_key, std::move(callback), network_context);
}

void PreconnectManagerImpl::TryToLaunchPreresolveJobs() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  // We assume that the number of jobs in the queue will be relatively small at
  // any given time. We can revisit this as needed.
  UMA_HISTOGRAM_COUNTS_100("Navigation.Preconnect.PreresolveJobQueueLength",
                           queued_jobs_.size());

  while (!queued_jobs_.empty() &&
         inflight_preresolves_count_ < kMaxInflightPreresolves) {
    auto job_id = queued_jobs_.front();
    queued_jobs_.pop_front();
    PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
    DCHECK(job);

    // Note: PreresolveJobs are put into |queued_jobs_| immediately on creation,
    // so their creation time is also the time at which they started queueing.
    UMA_HISTOGRAM_TIMES("Navigation.Preconnect.PreresolveJobQueueingTime",
                        base::TimeTicks::Now() - job->creation_time);

    PreresolveInfo* info = job->info;

    if (!(info && info->was_canceled)) {
      // This is used to avoid issuing DNS requests when a fixed proxy
      // configuration is in place, which improves efficiency, and is also
      // important if the unproxied DNS may contain incorrect entries.
      job->proxy_lookup_client = LookupProxyForUrl(
          job->url, job->network_anonymization_key,
          base::OptionalToPtr(job->storage_partition_config),
          base::BindOnce(&PreconnectManagerImpl::OnProxyLookupFinished,
                         weak_factory_.GetWeakPtr(), job_id));
      if (info) {
        ++info->inflight_count;
        if (delegate_) {
          delegate_->PreconnectInitiated(info->url, job->url);
        }
      }
      ++inflight_preresolves_count_;
    } else {
      preresolve_jobs_.Remove(job_id);
    }

    if (info) {
      DCHECK_LE(1u, info->queued_count);
      --info->queued_count;
      if (info->is_done()) {
        AllPreresolvesForUrlFinished(info);
      }
    }
  }
}

void PreconnectManagerImpl::OnPreresolveFinished(PreresolveJobId job_id,
                                                 bool success) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
  DCHECK(job);

  if (observer_) {
    observer_->OnPreresolveFinished(job->url, job->network_anonymization_key,
                                    job->connection_change_observer_client,
                                    success);
  }

  job->resolve_host_client = nullptr;
  FinishPreresolveJob(job_id, success);
}

void PreconnectManagerImpl::OnProxyLookupFinished(PreresolveJobId job_id,
                                                  bool success) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
  DCHECK(job);

  if (observer_) {
    observer_->OnProxyLookupFinished(job->url, job->network_anonymization_key,
                                     success);
  }

  job->proxy_lookup_client = nullptr;
  if (success) {
    FinishPreresolveJob(job_id, success);
  } else {
    job->resolve_host_client = PreresolveUrl(
        job->url, job->network_anonymization_key,
        base::OptionalToPtr(job->storage_partition_config),
        base::BindOnce(&PreconnectManagerImpl::OnPreresolveFinished,
                       weak_factory_.GetWeakPtr(), job_id));
  }
}

void PreconnectManagerImpl::FinishPreresolveJob(PreresolveJobId job_id,
                                                bool success) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  PreresolveJob* job = preresolve_jobs_.Lookup(job_id);
  DCHECK(job);

  bool need_preconnect = success && job->need_preconnect();
  if (need_preconnect) {
    PreconnectUrl(job->url, job->num_sockets, job->allow_credentials,
                  job->network_anonymization_key, job->traffic_annotation_tag,
                  base::OptionalToPtr(job->storage_partition_config),
                  std::move(job->keepalive_config),
                  std::move(job->connection_change_observer_client));
  }

  PreresolveInfo* info = job->info;
  if (info) {
    info->stats->requests_stats.emplace_back(url::Origin::Create(job->url),
                                             need_preconnect);
  }
  preresolve_jobs_.Remove(job_id);
  --inflight_preresolves_count_;
  if (info) {
    DCHECK_LE(1u, info->inflight_count);
    --info->inflight_count;
  }
  if (info && info->is_done()) {
    AllPreresolvesForUrlFinished(info);
  }
  TryToLaunchPreresolveJobs();
}

void PreconnectManagerImpl::AllPreresolvesForUrlFinished(PreresolveInfo* info) {
  DCHECK(info);
  DCHECK(info->is_done());
  auto it = preresolve_info_.find(info->url);
  CHECK(it != preresolve_info_.end());
  DCHECK(info == it->second.get());
  if (delegate_) {
    delegate_->PreconnectFinished(std::move(info->stats));
  }
  preresolve_info_.erase(it);
}

network::mojom::NetworkContext* PreconnectManagerImpl::GetNetworkContext(
    const content::StoragePartitionConfig* storage_partition_config) const {
  if (network_context_) {
    return network_context_;
  }

  auto* network_context =
      browser_context_
          ->GetStoragePartition(
              storage_partition_config
                  ? *storage_partition_config
                  : content::StoragePartitionConfig::CreateDefault(
                        browser_context_))
          ->GetNetworkContext();
  DCHECK(network_context);
  return network_context;
}

}  // namespace content