910e62b5创建于 1月15日历史提交
// Copyright 2018 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/cronet/native/url_request.h"

#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "components/cronet/cronet_upload_data_stream.h"
#include "components/cronet/native/engine.h"
#include "components/cronet/native/generated/cronet.idl_impl_struct.h"
#include "components/cronet/native/include/cronet_c.h"
#include "components/cronet/native/io_buffer_with_cronet_buffer.h"
#include "components/cronet/native/native_metrics_util.h"
#include "components/cronet/native/runnables.h"
#include "components/cronet/native/upload_data_sink.h"
#include "net/base/io_buffer.h"
#include "net/base/load_states.h"

namespace {

using RequestFinishedInfo = base::RefCountedData<Cronet_RequestFinishedInfo>;
using UrlResponseInfo = base::RefCountedData<Cronet_UrlResponseInfo>;
using CronetError = base::RefCountedData<Cronet_Error>;

template <typename T>
T* GetData(scoped_refptr<base::RefCountedData<T>> ptr) {
  return ptr == nullptr ? nullptr : &ptr->data;
}

net::RequestPriority ConvertRequestPriority(
    Cronet_UrlRequestParams_REQUEST_PRIORITY priority) {
  switch (priority) {
    case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_IDLE:
      return net::IDLE;
    case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_LOWEST:
      return net::LOWEST;
    case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_LOW:
      return net::LOW;
    case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_MEDIUM:
      return net::MEDIUM;
    case Cronet_UrlRequestParams_REQUEST_PRIORITY_REQUEST_PRIORITY_HIGHEST:
      return net::HIGHEST;
  }
  return net::DEFAULT_PRIORITY;
}

net::Idempotency ConvertIdempotency(
    Cronet_UrlRequestParams_IDEMPOTENCY idempotency) {
  switch (idempotency) {
    case Cronet_UrlRequestParams_IDEMPOTENCY_DEFAULT_IDEMPOTENCY:
      return net::DEFAULT_IDEMPOTENCY;
    case Cronet_UrlRequestParams_IDEMPOTENCY_IDEMPOTENT:
      return net::IDEMPOTENT;
    case Cronet_UrlRequestParams_IDEMPOTENCY_NOT_IDEMPOTENT:
      return net::NOT_IDEMPOTENT;
  }
  return net::DEFAULT_IDEMPOTENCY;
}

scoped_refptr<UrlResponseInfo> CreateCronet_UrlResponseInfo(
    const std::vector<std::string>& url_chain,
    int http_status_code,
    const std::string& http_status_text,
    const net::HttpResponseHeaders* headers,
    bool was_cached,
    const std::string& negotiated_protocol,
    const std::string& proxy_server,
    int64_t received_byte_count) {
  auto response_info = base::MakeRefCounted<UrlResponseInfo>();
  response_info->data.url = url_chain.back();
  response_info->data.url_chain = url_chain;
  response_info->data.http_status_code = http_status_code;
  response_info->data.http_status_text = http_status_text;
  // |headers| could be nullptr.
  if (headers != nullptr) {
    size_t iter = 0;
    std::string header_name;
    std::string header_value;
    while (headers->EnumerateHeaderLines(&iter, &header_name, &header_value)) {
      Cronet_HttpHeader header;
      header.name = header_name;
      header.value = header_value;
      response_info->data.all_headers_list.push_back(std::move(header));
    }
  }
  response_info->data.was_cached = was_cached;
  response_info->data.negotiated_protocol = negotiated_protocol;
  response_info->data.proxy_server = proxy_server;
  response_info->data.received_byte_count = received_byte_count;
  return response_info;
}

Cronet_Error_ERROR_CODE NetErrorToCronetErrorCode(int net_error) {
  switch (net_error) {
    case net::ERR_NAME_NOT_RESOLVED:
      return Cronet_Error_ERROR_CODE_ERROR_HOSTNAME_NOT_RESOLVED;
    case net::ERR_INTERNET_DISCONNECTED:
      return Cronet_Error_ERROR_CODE_ERROR_INTERNET_DISCONNECTED;
    case net::ERR_NETWORK_CHANGED:
      return Cronet_Error_ERROR_CODE_ERROR_NETWORK_CHANGED;
    case net::ERR_TIMED_OUT:
      return Cronet_Error_ERROR_CODE_ERROR_TIMED_OUT;
    case net::ERR_CONNECTION_CLOSED:
      return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_CLOSED;
    case net::ERR_CONNECTION_TIMED_OUT:
      return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_TIMED_OUT;
    case net::ERR_CONNECTION_REFUSED:
      return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_REFUSED;
    case net::ERR_CONNECTION_RESET:
      return Cronet_Error_ERROR_CODE_ERROR_CONNECTION_RESET;
    case net::ERR_ADDRESS_UNREACHABLE:
      return Cronet_Error_ERROR_CODE_ERROR_ADDRESS_UNREACHABLE;
    case net::ERR_QUIC_PROTOCOL_ERROR:
      return Cronet_Error_ERROR_CODE_ERROR_QUIC_PROTOCOL_FAILED;
    default:
      return Cronet_Error_ERROR_CODE_ERROR_OTHER;
  }
}

bool IsCronetErrorImmediatelyRetryable(Cronet_Error_ERROR_CODE error_code) {
  switch (error_code) {
    case Cronet_Error_ERROR_CODE_ERROR_HOSTNAME_NOT_RESOLVED:
    case Cronet_Error_ERROR_CODE_ERROR_INTERNET_DISCONNECTED:
    case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_REFUSED:
    case Cronet_Error_ERROR_CODE_ERROR_ADDRESS_UNREACHABLE:
    case Cronet_Error_ERROR_CODE_ERROR_OTHER:
    default:
      return false;
    case Cronet_Error_ERROR_CODE_ERROR_NETWORK_CHANGED:
    case Cronet_Error_ERROR_CODE_ERROR_TIMED_OUT:
    case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_CLOSED:
    case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_TIMED_OUT:
    case Cronet_Error_ERROR_CODE_ERROR_CONNECTION_RESET:
      return true;
  }
}

scoped_refptr<CronetError> CreateCronet_Error(int net_error,
                                              int quic_error,
                                              const std::string& error_string) {
  auto error = base::MakeRefCounted<CronetError>();
  error->data.error_code = NetErrorToCronetErrorCode(net_error);
  error->data.message = error_string;
  error->data.internal_error_code = net_error;
  error->data.quic_detailed_error_code = quic_error;
  error->data.immediately_retryable =
      IsCronetErrorImmediatelyRetryable(error->data.error_code);
  return error;
}

#if DCHECK_IS_ON()
// Runnable used to verify that Executor calls Cronet_Runnable_Destroy().
class VerifyDestructionRunnable : public Cronet_Runnable {
 public:
  VerifyDestructionRunnable(base::WaitableEvent* destroyed)
      : destroyed_(destroyed) {}

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

  // Signal event indicating Runnable was properly Destroyed.
  ~VerifyDestructionRunnable() override { destroyed_->Signal(); }

  void Run() override {}

 private:
  // Event indicating destructor is called.
  const raw_ptr<base::WaitableEvent> destroyed_;
};
#endif  // DCHECK_IS_ON()

// Convert net::LoadState to Cronet_UrlRequestStatusListener_Status.
Cronet_UrlRequestStatusListener_Status ConvertLoadState(
    net::LoadState load_state) {
  switch (load_state) {
    case net::LOAD_STATE_IDLE:
      return Cronet_UrlRequestStatusListener_Status_IDLE;

    case net::LOAD_STATE_WAITING_FOR_STALLED_SOCKET_POOL:
      return Cronet_UrlRequestStatusListener_Status_WAITING_FOR_STALLED_SOCKET_POOL;

    case net::LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET:
      return Cronet_UrlRequestStatusListener_Status_WAITING_FOR_AVAILABLE_SOCKET;

    case net::LOAD_STATE_WAITING_FOR_DELEGATE:
      return Cronet_UrlRequestStatusListener_Status_WAITING_FOR_DELEGATE;

    case net::LOAD_STATE_WAITING_FOR_CACHE:
      return Cronet_UrlRequestStatusListener_Status_WAITING_FOR_CACHE;

    case net::LOAD_STATE_DOWNLOADING_PAC_FILE:
      return Cronet_UrlRequestStatusListener_Status_DOWNLOADING_PAC_FILE;

    case net::LOAD_STATE_RESOLVING_PROXY_FOR_URL:
      return Cronet_UrlRequestStatusListener_Status_RESOLVING_PROXY_FOR_URL;

    case net::LOAD_STATE_RESOLVING_HOST_IN_PAC_FILE:
      return Cronet_UrlRequestStatusListener_Status_RESOLVING_HOST_IN_PAC_FILE;

    case net::LOAD_STATE_ESTABLISHING_PROXY_TUNNEL:
      return Cronet_UrlRequestStatusListener_Status_ESTABLISHING_PROXY_TUNNEL;

    case net::LOAD_STATE_RESOLVING_HOST:
      return Cronet_UrlRequestStatusListener_Status_RESOLVING_HOST;

    case net::LOAD_STATE_CONNECTING:
      return Cronet_UrlRequestStatusListener_Status_CONNECTING;

    case net::LOAD_STATE_SSL_HANDSHAKE:
      return Cronet_UrlRequestStatusListener_Status_SSL_HANDSHAKE;

    case net::LOAD_STATE_SENDING_REQUEST:
      return Cronet_UrlRequestStatusListener_Status_SENDING_REQUEST;

    case net::LOAD_STATE_WAITING_FOR_RESPONSE:
      return Cronet_UrlRequestStatusListener_Status_WAITING_FOR_RESPONSE;

    case net::LOAD_STATE_READING_RESPONSE:
      return Cronet_UrlRequestStatusListener_Status_READING_RESPONSE;

    default:
      // A load state is retrieved but there is no corresponding
      // request status. This most likely means that the mapping is
      // incorrect.
      NOTREACHED();
  }
}

}  // namespace

namespace cronet {

// NetworkTasks is owned by CronetURLRequest. It is constructed on client
// thread, but invoked and deleted on the network thread.
class Cronet_UrlRequestImpl::NetworkTasks : public CronetURLRequest::Callback {
 public:
  NetworkTasks(const std::string& url, Cronet_UrlRequestImpl* url_request);

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

  ~NetworkTasks() override = default;

  // Callback function used for GetStatus().
  void OnStatus(Cronet_UrlRequestStatusListenerPtr listener,
                net::LoadState load_state);

 private:
  // CronetURLRequest::Callback implementation:
  void OnReceivedRedirect(const std::string& new_location,
                          int http_status_code,
                          const std::string& http_status_text,
                          const net::HttpResponseHeaders* headers,
                          bool was_cached,
                          const std::string& negotiated_protocol,
                          const std::string& proxy_server,
                          int64_t received_byte_count) override;
  void OnResponseStarted(int http_status_code,
                         const std::string& http_status_text,
                         const net::HttpResponseHeaders* headers,
                         bool was_cached,
                         const std::string& negotiated_protocol,
                         const std::string& proxy_server,
                         int64_t received_byte_count) override;
  void OnReadCompleted(scoped_refptr<net::IOBuffer> buffer,
                       int bytes_read,
                       int64_t received_byte_count) override;
  void OnSucceeded(int64_t received_byte_count) override;
  void OnError(int net_error,
               int quic_error,
               quic::ConnectionCloseSource source,
               const std::string& error_string,
               int64_t received_byte_count) override;
  void OnCanceled() override;
  void OnDestroyed() override;
  void OnMetricsCollected(const base::Time& request_start_time,
                          const base::TimeTicks& request_start,
                          const base::TimeTicks& dns_start,
                          const base::TimeTicks& dns_end,
                          const base::TimeTicks& connect_start,
                          const base::TimeTicks& connect_end,
                          const base::TimeTicks& ssl_start,
                          const base::TimeTicks& ssl_end,
                          const base::TimeTicks& send_start,
                          const base::TimeTicks& send_end,
                          const base::TimeTicks& push_start,
                          const base::TimeTicks& push_end,
                          const base::TimeTicks& receive_headers_end,
                          const base::TimeTicks& request_end,
                          bool socket_reused,
                          int64_t sent_bytes_count,
                          int64_t received_bytes_count,
                          bool quic_connection_migration_attempted,
                          bool quic_connection_migration_successful)
      LOCKS_EXCLUDED(url_request_->lock_) override;

  // The UrlRequest which owns context that owns the callback.
  const raw_ptr<Cronet_UrlRequestImpl, AcrossTasksDanglingUntriaged>
      url_request_ = nullptr;

  // URL chain contains the URL currently being requested, and
  // all URLs previously requested. New URLs are added before
  // Cronet_UrlRequestCallback::OnRedirectReceived is called.
  std::vector<std::string> url_chain_;

  // Set to true when OnCanceled/OnSucceeded/OnFailed is posted.
  // When true it is unsafe to attempt to post other callbacks
  // like OnStatus because the request may be destroyed.
  bool final_callback_posted_ = false;

  // All methods except constructor are invoked on the network thread.
  THREAD_CHECKER(network_thread_checker_);
};

Cronet_UrlRequestImpl::Cronet_UrlRequestImpl() = default;

Cronet_UrlRequestImpl::~Cronet_UrlRequestImpl() {
  base::AutoLock lock(lock_);
  // Only request that has never started is allowed to exist at this point.
  // The app must wait for OnSucceeded / OnFailed / OnCanceled  callback before
  // destroying |this|.
  if (request_) {
    CHECK(!started_);
    DestroyRequestUnlessDoneLocked(
        Cronet_RequestFinishedInfo_FINISHED_REASON_SUCCEEDED);
  }
}

Cronet_RESULT Cronet_UrlRequestImpl::InitWithParams(
    Cronet_EnginePtr engine,
    Cronet_String url,
    Cronet_UrlRequestParamsPtr params,
    Cronet_UrlRequestCallbackPtr callback,
    Cronet_ExecutorPtr executor) {
  CHECK(engine);
  engine_ = reinterpret_cast<Cronet_EngineImpl*>(engine);
  if (!url || std::string(url).empty())
    return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_URL);
  if (!params)
    return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_PARAMS);
  if (!callback)
    return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_CALLBACK);
  if (!executor)
    return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_EXECUTOR);

  VLOG(1) << "New Cronet_UrlRequest: " << url;

  base::AutoLock lock(lock_);
  if (request_) {
    return engine_->CheckResult(
        Cronet_RESULT_ILLEGAL_STATE_REQUEST_ALREADY_INITIALIZED);
  }

  callback_ = callback;
  executor_ = executor;

  if (params->request_finished_listener != nullptr &&
      params->request_finished_executor == nullptr) {
    return engine_->CheckResult(
        Cronet_RESULT_NULL_POINTER_REQUEST_FINISHED_INFO_LISTENER_EXECUTOR);
  }

  request_finished_listener_ = params->request_finished_listener;
  request_finished_executor_ = params->request_finished_executor;
  // Copy, don't move -- this function isn't allowed to change |params|.
  annotations_ = params->annotations;

  auto network_tasks = std::make_unique<NetworkTasks>(url, this);
  network_tasks_ = network_tasks.get();

  request_ = new CronetURLRequest(
      engine_->cronet_url_request_context(), std::move(network_tasks),
      GURL(url), ConvertRequestPriority(params->priority),
      params->disable_cache, /*disable_connection_migration=*/true,
      // TODO(pauljensen): Consider exposing TrafficStats API via C++ API.
      /*traffic_stats_tag_set=*/false,
      /*traffic_stats_tag=*/0,
      /*traffic_stats_uid_set=*/false,
      /*traffic_stats_uid=*/0, ConvertIdempotency(params->idempotency),
      /*shared_dictionary=*/nullptr);

  if (params->upload_data_provider) {
    upload_data_sink_ = std::make_unique<Cronet_UploadDataSinkImpl>(
        this, params->upload_data_provider,
        params->upload_data_provider_executor
            ? params->upload_data_provider_executor
            : executor);
    upload_data_sink_->InitRequest(request_);
    request_->SetHttpMethod("POST");
  }

  if (!params->http_method.empty() &&
      !request_->SetHttpMethod(params->http_method)) {
    return engine_->CheckResult(
        Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_METHOD);
  }

  for (const auto& request_header : params->request_headers) {
    if (request_header.name.empty())
      return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_HEADER_NAME);
    if (request_header.value.empty())
      return engine_->CheckResult(Cronet_RESULT_NULL_POINTER_HEADER_VALUE);
    if (!request_->AddRequestHeader(request_header.name,
                                    request_header.value)) {
      return engine_->CheckResult(
          Cronet_RESULT_ILLEGAL_ARGUMENT_INVALID_HTTP_HEADER);
    }
  }
  return engine_->CheckResult(Cronet_RESULT_SUCCESS);
}

Cronet_RESULT Cronet_UrlRequestImpl::Start() {
  base::AutoLock lock(lock_);
  if (started_) {
    return engine_->CheckResult(
        Cronet_RESULT_ILLEGAL_STATE_REQUEST_ALREADY_STARTED);
  }
  if (!request_) {
    return engine_->CheckResult(
        Cronet_RESULT_ILLEGAL_STATE_REQUEST_NOT_INITIALIZED);
  }
#if DCHECK_IS_ON()
  Cronet_Executor_Execute(executor_,
                          new VerifyDestructionRunnable(&runnable_destroyed_));
#endif  // DCHECK_IS_ON()
  request_->Start();
  started_ = true;
  return engine_->CheckResult(Cronet_RESULT_SUCCESS);
}

Cronet_RESULT Cronet_UrlRequestImpl::FollowRedirect() {
  base::AutoLock lock(lock_);
  if (!waiting_on_redirect_) {
    return engine_->CheckResult(
        Cronet_RESULT_ILLEGAL_STATE_UNEXPECTED_REDIRECT);
  }
  waiting_on_redirect_ = false;
  if (!IsDoneLocked())
    request_->FollowDeferredRedirect();
  return engine_->CheckResult(Cronet_RESULT_SUCCESS);
}

Cronet_RESULT Cronet_UrlRequestImpl::Read(Cronet_BufferPtr buffer) {
  base::AutoLock lock(lock_);
  if (!waiting_on_read_)
    return engine_->CheckResult(Cronet_RESULT_ILLEGAL_STATE_UNEXPECTED_READ);
  waiting_on_read_ = false;
  if (IsDoneLocked()) {
    Cronet_Buffer_Destroy(buffer);
    return engine_->CheckResult(Cronet_RESULT_SUCCESS);
  }
  // Create IOBuffer that will own |buffer| while it is used by |request_|.
  net::IOBuffer* io_buffer = new IOBufferWithCronet_Buffer(buffer);
  if (request_->ReadData(io_buffer, Cronet_Buffer_GetSize(buffer)))
    return engine_->CheckResult(Cronet_RESULT_SUCCESS);
  return engine_->CheckResult(Cronet_RESULT_ILLEGAL_STATE_READ_FAILED);
}

void Cronet_UrlRequestImpl::Cancel() {
  base::AutoLock lock(lock_);
  if (started_) {
    // If request has posted callbacks to client executor, then it is possible
    // that |request_| will be destroyed before callback is executed. The
    // callback runnable uses IsDone() to avoid calling client callback in this
    // case.
    DestroyRequestUnlessDoneLocked(
        Cronet_RequestFinishedInfo_FINISHED_REASON_CANCELED);
  }
}

bool Cronet_UrlRequestImpl::IsDone() {
  base::AutoLock lock(lock_);
  return IsDoneLocked();
}

bool Cronet_UrlRequestImpl::IsDoneLocked() const {
  lock_.AssertAcquired();
  return started_ && request_ == nullptr;
}

bool Cronet_UrlRequestImpl::DestroyRequestUnlessDone(
    Cronet_RequestFinishedInfo_FINISHED_REASON finished_reason) {
  base::AutoLock lock(lock_);
  return DestroyRequestUnlessDoneLocked(finished_reason);
}

bool Cronet_UrlRequestImpl::DestroyRequestUnlessDoneLocked(
    Cronet_RequestFinishedInfo_FINISHED_REASON finished_reason) {
  lock_.AssertAcquired();
  if (request_ == nullptr)
    return true;
  DCHECK(error_ == nullptr ||
         finished_reason == Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED);
  request_->Destroy(finished_reason ==
                    Cronet_RequestFinishedInfo_FINISHED_REASON_CANCELED);
  // Request can no longer be used as CronetURLRequest::Destroy() will
  // eventually delete |request_| from the network thread, so setting |request_|
  // to nullptr doesn't introduce a memory leak.
  request_ = nullptr;
  return false;
}

void Cronet_UrlRequestImpl::GetStatus(
    Cronet_UrlRequestStatusListenerPtr listener) {
  {
    base::AutoLock lock(lock_);
    if (started_ && request_) {
      status_listeners_.insert(listener);
      // UnsafeDanglingUntriaged triggered by test:
      // UrlRequestTest.GetStatus
      // TODO(crbug.com/40061562): Remove `UnsafeDanglingUntriaged`
      request_->GetStatus(
          base::BindOnce(&Cronet_UrlRequestImpl::NetworkTasks::OnStatus,
                         base::Unretained(network_tasks_),
                         base::UnsafeDanglingUntriaged(listener)));
      return;
    }
  }
  PostTaskToExecutor(
      base::BindOnce(Cronet_UrlRequestStatusListener_OnStatus, listener,
                     Cronet_UrlRequestStatusListener_Status_INVALID));
}

void Cronet_UrlRequestImpl::PostCallbackOnFailedToExecutor() {
  PostTaskToExecutor(base::BindOnce(
      &Cronet_UrlRequestImpl::InvokeCallbackOnFailed, base::Unretained(this)));
}

void Cronet_UrlRequestImpl::OnUploadDataProviderError(
    const std::string& error_message) {
  base::AutoLock lock(lock_);
  // If |error_| is not nullptr, that means that another network error is
  // already reported.
  if (error_)
    return;
  error_ = CreateCronet_Error(
      0, 0, "Failure from UploadDataProvider: " + error_message);
  error_->data.error_code = Cronet_Error_ERROR_CODE_ERROR_CALLBACK;

  request_->MaybeReportMetricsAndRunCallback(
      base::BindOnce(&Cronet_UrlRequestImpl::PostCallbackOnFailedToExecutor,
                     base::Unretained(this)));
}

void Cronet_UrlRequestImpl::PostTaskToExecutor(base::OnceClosure task) {
  Cronet_RunnablePtr runnable =
      new cronet::OnceClosureRunnable(std::move(task));
  // |runnable| is passed to executor, which destroys it after execution.
  Cronet_Executor_Execute(executor_, runnable);
}

void Cronet_UrlRequestImpl::InvokeCallbackOnRedirectReceived(
    const std::string& new_location) {
  if (IsDone())
    return;
  Cronet_UrlRequestCallback_OnRedirectReceived(
      callback_, this, GetData(response_info_), new_location.c_str());
}

void Cronet_UrlRequestImpl::InvokeCallbackOnResponseStarted() {
  if (IsDone())
    return;
#if DCHECK_IS_ON()
  // Verify that Executor calls Cronet_Runnable_Destroy().
  if (!runnable_destroyed_.TimedWait(base::Seconds(5))) {
    LOG(ERROR) << "Cronet Executor didn't call Cronet_Runnable_Destroy() in "
                  "5s; still waiting.";
    runnable_destroyed_.Wait();
  }
#endif  // DCHECK_IS_ON()
  Cronet_UrlRequestCallback_OnResponseStarted(callback_, this,
                                              GetData(response_info_));
}

void Cronet_UrlRequestImpl::InvokeCallbackOnReadCompleted(
    std::unique_ptr<Cronet_Buffer> cronet_buffer,
    int bytes_read) {
  if (IsDone())
    return;
  Cronet_UrlRequestCallback_OnReadCompleted(
      callback_, this, GetData(response_info_), cronet_buffer.release(),
      bytes_read);
}

void Cronet_UrlRequestImpl::InvokeCallbackOnSucceeded() {
  if (DestroyRequestUnlessDone(
          Cronet_RequestFinishedInfo_FINISHED_REASON_SUCCEEDED)) {
    return;
  }
  InvokeAllStatusListeners();
  MaybeReportMetrics(Cronet_RequestFinishedInfo_FINISHED_REASON_SUCCEEDED);
  Cronet_UrlRequestCallback_OnSucceeded(callback_, this,
                                        GetData(response_info_));
  // |this| may have been deleted here.
}

void Cronet_UrlRequestImpl::InvokeCallbackOnFailed() {
  if (DestroyRequestUnlessDone(
          Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED)) {
    return;
  }
  InvokeAllStatusListeners();
  MaybeReportMetrics(Cronet_RequestFinishedInfo_FINISHED_REASON_FAILED);
  Cronet_UrlRequestCallback_OnFailed(callback_, this, GetData(response_info_),
                                     GetData(error_));
  // |this| may have been deleted here.
}

void Cronet_UrlRequestImpl::InvokeCallbackOnCanceled() {
  InvokeAllStatusListeners();
  MaybeReportMetrics(Cronet_RequestFinishedInfo_FINISHED_REASON_CANCELED);
  Cronet_UrlRequestCallback_OnCanceled(callback_, this,
                                       GetData(response_info_));
  // |this| may have been deleted here.
}

void Cronet_UrlRequestImpl::InvokeAllStatusListeners() {
  std::unordered_multiset<Cronet_UrlRequestStatusListenerPtr> status_listeners;
  {
    base::AutoLock lock(lock_);
    // Verify the request has already been destroyed, which ensures no more
    // status listeners can be added.
    DCHECK(!request_);
    status_listeners.swap(status_listeners_);
  }
  for (Cronet_UrlRequestStatusListener* status_listener : status_listeners) {
    Cronet_UrlRequestStatusListener_OnStatus(
        status_listener, Cronet_UrlRequestStatusListener_Status_INVALID);
  }
#if DCHECK_IS_ON()
  // Verify no status listeners added during OnStatus() callbacks.
  base::AutoLock lock(lock_);
  DCHECK(status_listeners_.empty());
#endif  // DCHECK_IS_ON()
}

void Cronet_UrlRequestImpl::MaybeReportMetrics(
    Cronet_RequestFinishedInfo_FINISHED_REASON finished_reason) {
  if (request_finished_info_ == nullptr)
    return;
  request_finished_info_->data.annotations = std::move(annotations_);
  request_finished_info_->data.finished_reason = finished_reason;

  engine_->ReportRequestFinished(request_finished_info_, response_info_,
                                 error_);
  if (request_finished_listener_ != nullptr) {
    DCHECK(request_finished_executor_ != nullptr);
    // Execute() owns and deletes the runnable.
    request_finished_executor_->Execute(
        new cronet::OnceClosureRunnable(base::BindOnce(
            [](Cronet_RequestFinishedInfoListenerPtr request_finished_listener,
               scoped_refptr<RequestFinishedInfo> request_finished_info,
               scoped_refptr<UrlResponseInfo> response_info,
               scoped_refptr<CronetError> error) {
              request_finished_listener->OnRequestFinished(
                  GetData(request_finished_info), GetData(response_info),
                  GetData(error));
            },
            request_finished_listener_, request_finished_info_, response_info_,
            error_)));
  }
}

Cronet_UrlRequestImpl::NetworkTasks::NetworkTasks(
    const std::string& url,
    Cronet_UrlRequestImpl* url_request)
    : url_request_(url_request), url_chain_({url}) {
  DETACH_FROM_THREAD(network_thread_checker_);
  DCHECK(url_request);
}

void Cronet_UrlRequestImpl::NetworkTasks::OnReceivedRedirect(
    const std::string& new_location,
    int http_status_code,
    const std::string& http_status_text,
    const net::HttpResponseHeaders* headers,
    bool was_cached,
    const std::string& negotiated_protocol,
    const std::string& proxy_server,
    int64_t received_byte_count) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  {
    base::AutoLock lock(url_request_->lock_);
    url_request_->waiting_on_redirect_ = true;
    url_request_->response_info_ = CreateCronet_UrlResponseInfo(
        url_chain_, http_status_code, http_status_text, headers, was_cached,
        negotiated_protocol, proxy_server, received_byte_count);
  }

  // Have to do this after creating responseInfo.
  url_chain_.push_back(new_location);

  // Invoke Cronet_UrlRequestCallback_OnRedirectReceived on client executor.
  url_request_->PostTaskToExecutor(
      base::BindOnce(&Cronet_UrlRequestImpl::InvokeCallbackOnRedirectReceived,
                     base::Unretained(url_request_), new_location));
}

void Cronet_UrlRequestImpl::NetworkTasks::OnResponseStarted(
    int http_status_code,
    const std::string& http_status_text,
    const net::HttpResponseHeaders* headers,
    bool was_cached,
    const std::string& negotiated_protocol,
    const std::string& proxy_server,
    int64_t received_byte_count) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  {
    base::AutoLock lock(url_request_->lock_);
    url_request_->waiting_on_read_ = true;
    url_request_->response_info_ = CreateCronet_UrlResponseInfo(
        url_chain_, http_status_code, http_status_text, headers, was_cached,
        negotiated_protocol, proxy_server, received_byte_count);
  }

  if (url_request_->upload_data_sink_)
    url_request_->upload_data_sink_->PostCloseToExecutor();

  // Invoke Cronet_UrlRequestCallback_OnResponseStarted on client executor.
  url_request_->PostTaskToExecutor(
      base::BindOnce(&Cronet_UrlRequestImpl::InvokeCallbackOnResponseStarted,
                     base::Unretained(url_request_)));
}

void Cronet_UrlRequestImpl::NetworkTasks::OnReadCompleted(
    scoped_refptr<net::IOBuffer> buffer,
    int bytes_read,
    int64_t received_byte_count) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  IOBufferWithCronet_Buffer* io_buffer =
      reinterpret_cast<IOBufferWithCronet_Buffer*>(buffer.get());
  std::unique_ptr<Cronet_Buffer> cronet_buffer(io_buffer->Release());
  {
    base::AutoLock lock(url_request_->lock_);
    url_request_->waiting_on_read_ = true;
    url_request_->response_info_->data.received_byte_count =
        received_byte_count;
  }

  // Invoke Cronet_UrlRequestCallback_OnReadCompleted on client executor.
  url_request_->PostTaskToExecutor(base::BindOnce(
      &Cronet_UrlRequestImpl::InvokeCallbackOnReadCompleted,
      base::Unretained(url_request_), std::move(cronet_buffer), bytes_read));
}

void Cronet_UrlRequestImpl::NetworkTasks::OnSucceeded(
    int64_t received_byte_count) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  {
    base::AutoLock lock(url_request_->lock_);
    url_request_->response_info_->data.received_byte_count =
        received_byte_count;
  }

  // Invoke Cronet_UrlRequestCallback_OnSucceeded on client executor.
  url_request_->PostTaskToExecutor(
      base::BindOnce(&Cronet_UrlRequestImpl::InvokeCallbackOnSucceeded,
                     base::Unretained(url_request_)));
  final_callback_posted_ = true;
}

void Cronet_UrlRequestImpl::NetworkTasks::OnError(
    int net_error,
    int quic_error,
    quic::ConnectionCloseSource source,
    const std::string& error_string,
    int64_t received_byte_count) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  {
    base::AutoLock lock(url_request_->lock_);
    if (url_request_->response_info_)
      url_request_->response_info_->data.received_byte_count =
          received_byte_count;
    url_request_->error_ =
        CreateCronet_Error(net_error, quic_error, error_string);
  }

  if (url_request_->upload_data_sink_)
    url_request_->upload_data_sink_->PostCloseToExecutor();

  // Invoke Cronet_UrlRequestCallback_OnFailed on client executor.
  url_request_->PostTaskToExecutor(
      base::BindOnce(&Cronet_UrlRequestImpl::InvokeCallbackOnFailed,
                     base::Unretained(url_request_)));
  final_callback_posted_ = true;
}

void Cronet_UrlRequestImpl::NetworkTasks::OnCanceled() {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  if (url_request_->upload_data_sink_)
    url_request_->upload_data_sink_->PostCloseToExecutor();

  // Invoke Cronet_UrlRequestCallback_OnCanceled on client executor.
  url_request_->PostTaskToExecutor(
      base::BindOnce(&Cronet_UrlRequestImpl::InvokeCallbackOnCanceled,
                     base::Unretained(url_request_)));
  final_callback_posted_ = true;
}

void Cronet_UrlRequestImpl::NetworkTasks::OnDestroyed() {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  DCHECK(url_request_);
}

void Cronet_UrlRequestImpl::NetworkTasks::OnMetricsCollected(
    const base::Time& request_start_time,
    const base::TimeTicks& request_start,
    const base::TimeTicks& dns_start,
    const base::TimeTicks& dns_end,
    const base::TimeTicks& connect_start,
    const base::TimeTicks& connect_end,
    const base::TimeTicks& ssl_start,
    const base::TimeTicks& ssl_end,
    const base::TimeTicks& send_start,
    const base::TimeTicks& send_end,
    const base::TimeTicks& push_start,
    const base::TimeTicks& push_end,
    const base::TimeTicks& receive_headers_end,
    const base::TimeTicks& request_end,
    bool socket_reused,
    int64_t sent_bytes_count,
    int64_t received_bytes_count,
    bool,  // quic_connection_migration_attempted
    bool   // quic_connection_migration_successful
) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  base::AutoLock lock(url_request_->lock_);
  DCHECK_EQ(url_request_->request_finished_info_, nullptr)
      << "Metrics collection should only happen once.";
  url_request_->request_finished_info_ =
      base::MakeRefCounted<RequestFinishedInfo>();
  auto& metrics = url_request_->request_finished_info_->data.metrics;
  metrics.emplace();
  using native_metrics_util::ConvertTime;
  ConvertTime(request_start, request_start, request_start_time,
              &metrics->request_start);
  ConvertTime(dns_start, request_start, request_start_time,
              &metrics->dns_start);
  ConvertTime(dns_end, request_start, request_start_time, &metrics->dns_end);
  ConvertTime(connect_start, request_start, request_start_time,
              &metrics->connect_start);
  ConvertTime(connect_end, request_start, request_start_time,
              &metrics->connect_end);
  ConvertTime(ssl_start, request_start, request_start_time,
              &metrics->ssl_start);
  ConvertTime(ssl_end, request_start, request_start_time, &metrics->ssl_end);
  ConvertTime(send_start, request_start, request_start_time,
              &metrics->sending_start);
  ConvertTime(send_end, request_start, request_start_time,
              &metrics->sending_end);
  ConvertTime(push_start, request_start, request_start_time,
              &metrics->push_start);
  ConvertTime(push_end, request_start, request_start_time, &metrics->push_end);
  ConvertTime(receive_headers_end, request_start, request_start_time,
              &metrics->response_start);
  ConvertTime(request_end, request_start, request_start_time,
              &metrics->request_end);
  metrics->socket_reused = socket_reused;
  metrics->sent_byte_count = sent_bytes_count;
  metrics->received_byte_count = received_bytes_count;
}

void Cronet_UrlRequestImpl::NetworkTasks::OnStatus(
    Cronet_UrlRequestStatusListenerPtr listener,
    net::LoadState load_state) {
  DCHECK_CALLED_ON_VALID_THREAD(network_thread_checker_);
  if (final_callback_posted_)
    return;
  {
    base::AutoLock lock(url_request_->lock_);
    auto element = url_request_->status_listeners_.find(listener);
    CHECK(element != url_request_->status_listeners_.end());
    url_request_->status_listeners_.erase(element);
  }

  // Invoke Cronet_UrlRequestCallback_OnCanceled on client executor.
  url_request_->PostTaskToExecutor(
      base::BindOnce(&Cronet_UrlRequestStatusListener_OnStatus, listener,
                     ConvertLoadState(load_state)));
}

}  // namespace cronet

CRONET_EXPORT Cronet_UrlRequestPtr Cronet_UrlRequest_Create() {
  return new cronet::Cronet_UrlRequestImpl();
}