// 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/service_worker/service_worker_new_script_loader.h"

#include <memory>
#include <vector>

#include "base/byte_size.h"
#include "base/containers/span.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/connection_allowlist_utils.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_cache_writer.h"
#include "content/browser/service_worker/service_worker_consts.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_loader_helpers.h"
#include "content/browser/service_worker/service_worker_metrics.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/worker_host/network_restrictions_worker_throttle.h"
#include "content/common/features.h"
#include "content/public/browser/url_loader_throttles.h"
#include "content/public/common/content_client.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_status_flags.h"
#include "net/http/http_response_info.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/common/loader/throttling_url_loader.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_args.h"

namespace content {

namespace {
constexpr char kServiceWorkerNewScriptLoaderScope[] =
    "ServiceWorkerNewScriptLoader";
}  // namespace

// We chose this size because the AppCache uses this.
const uint32_t ServiceWorkerNewScriptLoader::kReadBufferSize = 32768;

// This is for debugging https://crbug.com/959627.
// The purpose is to see where the IOBuffer comes from by checking |__vfptr|.
class ServiceWorkerNewScriptLoader::WrappedIOBuffer
    : public net::WrappedIOBuffer {
 public:
  explicit WrappedIOBuffer(base::span<const char> data)
      : net::WrappedIOBuffer(data) {}

 private:
  ~WrappedIOBuffer() override = default;

  // This is to make sure that the vtable is not merged with other classes.
  virtual void dummy() { NOTREACHED(); }
};

std::unique_ptr<ServiceWorkerNewScriptLoader>
ServiceWorkerNewScriptLoader::CreateAndStart(
    int32_t request_id,
    uint32_t options,
    const network::ResourceRequest& original_request,
    mojo::PendingRemote<network::mojom::URLLoaderClient> client,
    scoped_refptr<ServiceWorkerVersion> version,
    scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
    int64_t cache_resource_id,
    bool is_throttle_needed,
    const GlobalRenderFrameHostId& requesting_frame_id,
    const base::UnguessableToken& worker_network_restrictions_id) {
  return base::WrapUnique(new ServiceWorkerNewScriptLoader(
      request_id, options, original_request, std::move(client), version,
      loader_factory, traffic_annotation, cache_resource_id, is_throttle_needed,
      requesting_frame_id, worker_network_restrictions_id));
}

// TODO(nhiroki): We're doing multiple things in the ctor. Consider factors out
// some of them into a separate function.
ServiceWorkerNewScriptLoader::ServiceWorkerNewScriptLoader(
    int32_t request_id,
    uint32_t options,
    const network::ResourceRequest& original_request,
    mojo::PendingRemote<network::mojom::URLLoaderClient> client,
    scoped_refptr<ServiceWorkerVersion> version,
    scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
    int64_t cache_resource_id,
    bool is_throttle_needed,
    const GlobalRenderFrameHostId& requesting_frame_id,
    const base::UnguessableToken& worker_network_restrictions_id)
    : request_id_(request_id),
      request_url_(original_request.url),
      is_main_script_(original_request.destination ==
                          network::mojom::RequestDestination::kServiceWorker &&
                      original_request.mode ==
                          network::mojom::RequestMode::kSameOrigin),
      should_update_policy_container_(
          is_main_script_ &&
          (!base::FeatureList::IsEnabled(
               features::kServiceWorkerVerifyMainScriptUrl) ||
           original_request.url == version->script_url())),
      original_options_(options),
      version_(version),
      network_watcher_(FROM_HERE,
                       mojo::SimpleWatcher::ArmingPolicy::MANUAL,
                       base::SequencedTaskRunner::GetCurrentDefault()),
      loader_factory_(std::move(loader_factory)),
      client_(std::move(client)),
      client_producer_watcher_(FROM_HERE,
                               mojo::SimpleWatcher::ArmingPolicy::MANUAL,
                               base::SequencedTaskRunner::GetCurrentDefault()),
      requesting_frame_id_(requesting_frame_id),
      worker_network_restrictions_id_(worker_network_restrictions_id),
      creator_policies_(version_->creator_policies().Clone()) {
  ServiceWorkerMetrics::RecordMainScriptRequestValidationResult(
      service_worker_loader_helpers::ValidateMainScriptRequest(original_request,
                                                               *version));

  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::ServiceWorkerNewScriptLoader",
              perfetto::Flow::ProcessScoped(request_id_,
                                            kServiceWorkerNewScriptLoaderScope),
              "request_url", request_url_);
  CHECK_NE(cache_resource_id, blink::mojom::kInvalidServiceWorkerResourceId);

  network::ResourceRequest resource_request(original_request);
#if DCHECK_IS_ON()
  service_worker_loader_helpers::CheckVersionStatusBeforeWorkerScriptLoad(
      version_->status(), is_main_script_, version_->script_type());
#endif  // DCHECK_IS_ON()

  ServiceWorkerContextCore* context = version_->context().get();
  CHECK(context);
  scoped_refptr<ServiceWorkerRegistration> registration =
      context->GetLiveRegistration(version_->registration_id());
  // ServiceWorkerVersion keeps the registration alive while the service
  // worker is starting up, and it must be starting up here.
  CHECK(registration);

  // We need to filter on mode, since module imports use kServiceWorker as
  // destination, but only top level module scripts are same-origin.
  if (is_main_script_) {
    // Request SSLInfo. It will be persisted in service worker storage and
    // may be used by ServiceWorkerMainResourceLoader for navigations handled
    // by this service worker.
    options |= network::mojom::kURLLoadOptionSendSSLInfoWithResponse;
    resource_request.headers.SetHeader("Service-Worker", "script");
  }

  // Validate the browser cache if needed, e.g., updateViaCache demands it or 24
  // hours passed since the last update check that hit network.
  base::TimeDelta time_since_last_check =
      base::Time::Now() - registration->last_update_check();
  if (service_worker_loader_helpers::ShouldValidateBrowserCacheForScript(
          is_main_script_, version_->force_bypass_cache_for_scripts(),
          registration->update_via_cache(), time_since_last_check)) {
    resource_request.load_flags |= net::LOAD_VALIDATE_CACHE;
  }

  // Because the flag indicating whether decoding has occurred is not stored in
  // Service Worker Storage, we always decode on the network service side.
  resource_request.client_side_content_decoding_enabled = false;

  mojo::Remote<storage::mojom::ServiceWorkerResourceWriter> writer;
  context->registry().GetRemoteStorageControl()->CreateResourceWriter(
      cache_resource_id, writer.BindNewPipeAndPassReceiver());

  cache_writer_ = ServiceWorkerCacheWriter::CreateForWriteBack(
      std::move(writer), cache_resource_id);

  version_->script_cache_map()->NotifyStartedCaching(request_url_,
                                                     cache_resource_id);

  // Disable MIME sniffing. The spec requires the header list to have a
  // JavaScript MIME type. Therefore, no sniffing is needed.
  options &= ~network::mojom::kURLLoadOptionSniffMimeType;

  std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles;
  if (is_throttle_needed && context && context->wrapper() &&
      context->wrapper()->storage_partition()) {
    // A service worker is independent from WebContents and FrameTreeNode.
    // Return null or empty values when queried for either.
    base::RepeatingCallback<WebContents*()> web_contents_getter =
        base::BindRepeating([]() -> WebContents* { return nullptr; });
    throttles = CreateContentBrowserURLLoaderThrottles(
        resource_request, context->wrapper()->browser_context(),
        std::move(web_contents_getter),
        /*navigation_ui_data=*/nullptr, FrameTreeNodeId(),
        /*navigation_id=*/std::nullopt);

    // `rfh` may be null if the worker is being installed without a creator
    // frame (e.g., browser-initiated update). In this case, we cannot log
    // WebFeatures on the frame, and the script fetch itself is not restricted
    // based on a creator frame's active restrictions.
    // However, we still create the throttle to enforce connection allowlists
    // for the service worker's future subresource requests. These are
    // determined either from the script's response headers or from the
    // persisted `creator_policies_` (restored from the database) if the worker
    // inherits them.
    auto* rfh = RenderFrameHostImpl::FromID(requesting_frame_id_);
    if (auto throttle = NetworkRestrictionsWorkerThrottle::Create(
            context->wrapper()->storage_partition()->GetWeakPtr(),
            worker_network_restrictions_id_, creator_policies_.Clone(),
            rfh ? rfh->GetWeakPtr() : nullptr,
            /*is_service_worker=*/true)) {
      throttles.push_back(std::move(throttle));
    }
  }

  network_loader_ = blink::ThrottlingURLLoader::CreateLoaderAndStart(
      std::move(loader_factory_), std::move(throttles), request_id, options,
      &resource_request, this,
      net::NetworkTrafficAnnotationTag(traffic_annotation),
      base::SingleThreadTaskRunner::GetCurrentDefault());

  CHECK_EQ(LoaderState::kNotStarted, network_loader_state_);
  network_loader_state_ = LoaderState::kLoadingHeader;
}

ServiceWorkerNewScriptLoader::~ServiceWorkerNewScriptLoader() {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::~ServiceWorkerNewScriptLoader",
              perfetto::TerminatingFlow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  // This class is used as a SelfOwnedReceiver and its lifetime is tied to the
  // corresponding mojo connection. There could be cases where the mojo
  // connection is disconnected while writing the response to the storage.
  // Complete this loader with ERR_FAILED in such cases to update the script
  // cache map.
  bool writers_completed = header_writer_state_ == WriterState::kCompleted &&
                           body_writer_state_ == WriterState::kCompleted;
  if (network_loader_state_ == LoaderState::kCompleted && !writers_completed) {
    CHECK(client_);
    CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_FAILED),
                    ServiceWorkerConsts::kServiceWorkerInvalidVersionError,
                    nullptr);
  }
}

void ServiceWorkerNewScriptLoader::FollowRedirect(
    network::HttpRequestHeadersUpdateParams headers_update_params,
    const std::optional<GURL>& new_url) {
  // Resource requests for service worker scripts should not follow redirects.
  // See comments in OnReceiveRedirect().
  NOTREACHED();
}

void ServiceWorkerNewScriptLoader::SetPriority(net::RequestPriority priority,
                                               int32_t intra_priority_value) {
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::SetPriority",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  if (network_loader_)
    network_loader_->SetPriority(priority, intra_priority_value);
}

// URLLoaderClient for network loader ------------------------------------------

void ServiceWorkerNewScriptLoader::OnReceiveEarlyHints(
    network::mojom::EarlyHintsPtr early_hints) {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnReceiveEarlyHints",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
}

void ServiceWorkerNewScriptLoader::OnReceiveResponse(
    network::mojom::URLResponseHeadPtr response_head,
    mojo::ScopedDataPipeConsumerHandle body,
    std::optional<mojo_base::BigBuffer> cached_metadata) {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnReceiveResponse",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  CHECK_EQ(LoaderState::kLoadingHeader, network_loader_state_);
  if (!version_->context() || version_->is_redundant()) {
    CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_FAILED),
                    ServiceWorkerConsts::kServiceWorkerInvalidVersionError,
                    std::move(response_head));
    return;
  }

  blink::ServiceWorkerStatusCode service_worker_state =
      blink::ServiceWorkerStatusCode::kOk;
  network::URLLoaderCompletionStatus completion_status;
  std::string error_message;
  if (!service_worker_loader_helpers::CheckResponseHead(
          *response_head, &service_worker_state, &completion_status,
          &error_message)) {
    CHECK_NE(net::OK, completion_status.error_code);
    CommitCompleted(completion_status, error_message, std::move(response_head));
    return;
  }

  if (should_update_policy_container_) {
    // Check the path restriction defined in the spec:
    // https://w3c.github.io/ServiceWorker/#service-worker-script-response
    std::optional<std::string_view> service_worker_allowed =
        response_head->headers->EnumerateHeader(
            nullptr, ServiceWorkerConsts::kServiceWorkerAllowed);
    if (!service_worker_loader_helpers::IsPathRestrictionSatisfied(
            version_->scope(), request_url_, service_worker_allowed,
            &error_message)) {
      CommitCompleted(
          network::URLLoaderCompletionStatus(net::ERR_INSECURE_RESPONSE),
          error_message, std::move(response_head));
      return;
    }

    if (!GetContentClient()
             ->browser()
             ->ShouldServiceWorkerInheritPolicyContainerFromCreator(
                 request_url_)) {
      PolicyContainerPolicies policies =
          response_head->parsed_headers
              // This does not parse the referrer policy, which will be
              // updated in ServiceWorkerGlobalScope::Initialize
              ? PolicyContainerPolicies(request_url_, response_head.get(),
                                        /*client=*/nullptr)
              : PolicyContainerPolicies();
      policies.connection_allowlists = GetConnectionAllowlistsForWorker(
          request_url_, response_head.get(), &creator_policies_,
          /*inherit_from_creator=*/false);
      version_->SetPolicyContainerHost(
          base::MakeRefCounted<PolicyContainerHost>(std::move(policies)));
    }

    if (response_head->network_accessed)
      version_->embedded_worker()->OnNetworkAccessedForScriptLoad();

    version_->SetMainScriptResponse(
        std::make_unique<ServiceWorkerVersion::MainScriptResponse>(
            *response_head));
  }

  WriteHeaders(response_head.Clone());

  // WriteHeaders() can commit completed.
  if (network_loader_state_ == LoaderState::kCompleted &&
      header_writer_state_ == WriterState::kCompleted &&
      body_writer_state_ == WriterState::kCompleted) {
    return;
  }

  // Don't pass SSLInfo to the client when the original request doesn't ask
  // to send it.
  if (response_head->ssl_info.has_value() &&
      !(original_options_ &
        network::mojom::kURLLoadOptionSendSSLInfoWithResponse)) {
    response_head->ssl_info.reset();
  }

  if (!body) {
    client_->OnReceiveResponse(std::move(response_head),
                               mojo::ScopedDataPipeConsumerHandle(),
                               std::move(cached_metadata));
    return;
  }

  // Create a pair of the consumer and producer for responding to the client.
  mojo::ScopedDataPipeConsumerHandle client_consumer;
  if (mojo::CreateDataPipe(nullptr, client_producer_, client_consumer) !=
      MOJO_RESULT_OK) {
    CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_FAILED),
                    ServiceWorkerConsts::kServiceWorkerFetchScriptError,
                    std::move(response_head));
    return;
  }

  // Pass the consumer handle for responding with the response to the client.
  client_->OnReceiveResponse(std::move(response_head),
                             std::move(client_consumer),
                             std::move(cached_metadata));

  client_producer_watcher_.Watch(
      client_producer_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
      base::BindRepeating(&ServiceWorkerNewScriptLoader::OnClientWritable,
                          weak_factory_.GetWeakPtr()));

  network_consumer_ = std::move(body);
  network_loader_state_ = LoaderState::kLoadingBody;
  MaybeStartNetworkConsumerHandleWatcher();
}

void ServiceWorkerNewScriptLoader::OnReceiveRedirect(
    const net::RedirectInfo& redirect_info,
    network::mojom::URLResponseHeadPtr response_head) {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnReceiveRedirect",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  // Resource requests for service worker scripts should not follow redirects.
  //
  // Step 9.5: "Set request's redirect mode to "error"."
  // https://w3c.github.io/ServiceWorker/#update-algorithm
  //
  // TODO(crbug.com/40595655): Follow redirects for imported scripts.
  CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT),
                  ServiceWorkerConsts::kServiceWorkerRedirectError,
                  std::move(response_head));
}

void ServiceWorkerNewScriptLoader::OnUploadProgress(
    int64_t current_position,
    int64_t total_size,
    OnUploadProgressCallback ack_callback) {
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::OnUploadProgress",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  client_->OnUploadProgress(current_position, total_size,
                            std::move(ack_callback));
}

void ServiceWorkerNewScriptLoader::OnTransferSizeUpdated(
    int32_t transfer_size_diff) {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnTransferSizeUpdated",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  client_->OnTransferSizeUpdated(transfer_size_diff);
}

void ServiceWorkerNewScriptLoader::OnComplete(
    const network::URLLoaderCompletionStatus& status) {
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::OnComplete",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  LoaderState previous_state = network_loader_state_;
  network_loader_state_ = LoaderState::kCompleted;
  if (status.error_code != net::OK) {
    CommitCompleted(status, ServiceWorkerConsts::kServiceWorkerFetchScriptError,
                    nullptr);
    return;
  }

  CHECK_EQ(LoaderState::kLoadingBody, previous_state);

  switch (body_writer_state_) {
    case WriterState::kNotStarted:
      // The header is still being written. Wait until both the header and body
      // are written. OnNetworkDataAvailable() will call CommitCompleted() after
      // all data from |network_consumer_| is consumed.
      CHECK_EQ(WriterState::kWriting, header_writer_state_);
      return;
    case WriterState::kWriting:
      // Wait until it's written. OnNetworkDataAvailable() will call
      // CommitCompleted() after all data from |network_consumer_| is
      // consumed.
      CHECK_EQ(WriterState::kCompleted, header_writer_state_);
      return;
    case WriterState::kCompleted:
      CHECK_EQ(WriterState::kCompleted, header_writer_state_);
      CommitCompleted(network::URLLoaderCompletionStatus(net::OK),
                      std::string() /* status_message */, nullptr);
      return;
  }
  NOTREACHED() << static_cast<int>(body_writer_state_);
}

// End of URLLoaderClient ------------------------------------------------------

void ServiceWorkerNewScriptLoader::WriteHeaders(
    network::mojom::URLResponseHeadPtr response_head) {
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::WriteHeaders",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  CHECK_EQ(WriterState::kNotStarted, header_writer_state_);
  header_writer_state_ = WriterState::kWriting;
  cache_writer_->MaybeWriteHeaders(
      std::move(response_head),
      base::BindOnce(&ServiceWorkerNewScriptLoader::OnWriteHeadersComplete,
                     weak_factory_.GetWeakPtr()));
}

void ServiceWorkerNewScriptLoader::OnWriteHeadersComplete(net::Error error) {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnWriteHeadersComplete",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  CHECK_EQ(WriterState::kWriting, header_writer_state_);
  CHECK_NE(net::ERR_IO_PENDING, error);
  if (error != net::OK) {
    ServiceWorkerMetrics::CountWriteResponseResult(
        ServiceWorkerMetrics::WRITE_HEADERS_ERROR);
    CommitCompleted(network::URLLoaderCompletionStatus(error),
                    ServiceWorkerConsts::kDatabaseErrorMessage, nullptr);
    return;
  }
  header_writer_state_ = WriterState::kCompleted;

  // If all other states are kCompleted the response body is empty, we can
  // finish now.
  if (network_loader_state_ == LoaderState::kCompleted &&
      body_writer_state_ == WriterState::kCompleted) {
    CommitCompleted(network::URLLoaderCompletionStatus(net::OK),
                    std::string() /* status_message */, nullptr);
    return;
  }

  MaybeStartNetworkConsumerHandleWatcher();
}

void ServiceWorkerNewScriptLoader::MaybeStartNetworkConsumerHandleWatcher() {
  TRACE_EVENT(
      "ServiceWorker",
      "ServiceWorkerNewScriptLoader::MaybeStartNetworkConsumerHandleWatcher",
      perfetto::Flow::ProcessScoped(request_id_,
                                    kServiceWorkerNewScriptLoaderScope));
  if (network_loader_state_ == LoaderState::kLoadingHeader) {
    // OnReceiveResponse() or OnComplete() will continue the sequence.
    return;
  }

  if (header_writer_state_ != WriterState::kCompleted) {
    CHECK_EQ(WriterState::kWriting, header_writer_state_);
    // OnWriteHeadersComplete() will continue the sequence.
    return;
  }

  if (body_writer_state_ != WriterState::kNotStarted) {
    static bool has_dumped_without_crashing = false;
    if (!has_dumped_without_crashing) {
      has_dumped_without_crashing = true;
      SCOPED_CRASH_KEY_NUMBER("SWNewScriptLoader", "network_loader_state",
                              static_cast<int>(network_loader_state_));
      SCOPED_CRASH_KEY_NUMBER("SWNewScriptLoader", "header_writer_state",
                              static_cast<int>(header_writer_state_));
      SCOPED_CRASH_KEY_NUMBER("SWNewScriptLoader", "body_writer_state",
                              static_cast<int>(body_writer_state_));
      base::debug::DumpWithoutCrashing();
    }
    return;
  }
  body_writer_state_ = WriterState::kWriting;

  network_watcher_.Watch(
      network_consumer_.get(),
      MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
      base::BindRepeating(&ServiceWorkerNewScriptLoader::OnNetworkDataAvailable,
                          weak_factory_.GetWeakPtr()));
  network_watcher_.ArmOrNotify();
}

void ServiceWorkerNewScriptLoader::OnNetworkDataAvailable(MojoResult) {
  CHECK_EQ(WriterState::kCompleted, header_writer_state_);
  CHECK_EQ(WriterState::kWriting, body_writer_state_);
  CHECK(network_consumer_.is_valid());
  scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer;
  MojoResult result = network::MojoToNetPendingBuffer::BeginRead(
      &network_consumer_, &pending_buffer);
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnNetworkDataAvailable",
              perfetto::Flow::ProcessScoped(request_id_,
                                            kServiceWorkerNewScriptLoaderScope),
              "begin_read_result", result);
  switch (result) {
    case MOJO_RESULT_OK: {
      const uint32_t bytes_available = pending_buffer->size();
      WriteData(std::move(pending_buffer), bytes_available);
      return;
    }
    case MOJO_RESULT_FAILED_PRECONDITION: {
      // Call WriteData() with null buffer to let the cache writer know that
      // body from the network reaches to the end.
      WriteData(/*pending_buffer=*/nullptr, /*bytes_available=*/0);
      return;
    }
    case MOJO_RESULT_SHOULD_WAIT: {
      network_watcher_.ArmOrNotify();
      return;
    }
  }
  NOTREACHED() << static_cast<int>(result);
}

void ServiceWorkerNewScriptLoader::WriteData(
    scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
    uint32_t bytes_available) {
  auto buffer = base::MakeRefCounted<WrappedIOBuffer>(
      pending_buffer ? base::span(*pending_buffer) : base::span<const char>());

  // Cap the buffer size up to |kReadBufferSize|. The remaining will be written
  // next time.
  base::span<const uint8_t> bytes = buffer->span();
  bytes = bytes.first(std::min<size_t>(kReadBufferSize, bytes_available));

  size_t bytes_written = 0;
  MojoResult result = client_producer_->WriteData(
      bytes, MOJO_WRITE_DATA_FLAG_NONE, bytes_written);
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::WriteData",
              perfetto::Flow::ProcessScoped(request_id_,
                                            kServiceWorkerNewScriptLoaderScope),
              "write_data_result", result);
  switch (result) {
    case MOJO_RESULT_OK:
      break;
    case MOJO_RESULT_FAILED_PRECONDITION:
      ServiceWorkerMetrics::CountWriteResponseResult(
          ServiceWorkerMetrics::WRITE_DATA_ERROR);
      CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_FAILED),
                      ServiceWorkerConsts::kServiceWorkerFetchScriptError,
                      nullptr);
      return;
    case MOJO_RESULT_SHOULD_WAIT:
      DCHECK(pending_buffer);
      DCHECK(!pending_network_buffer_);
      DCHECK_EQ(pending_network_bytes_available_, 0u);
      // No data was written to `client_producer_` because the pipe was full.
      // Retry when the pipe becomes ready again.
      pending_network_buffer_ = std::move(pending_buffer);
      pending_network_bytes_available_ = bytes_available;
      client_producer_watcher_.ArmOrNotify();
      return;
    default:
      NOTREACHED() << static_cast<int>(result);
  }

  // Write the buffer in the service worker script storage up to the size we
  // successfully wrote to the data pipe (i.e., |bytes_written|).
  // A null buffer and zero |bytes_written| are passed when this is the end of
  // the body.
  cache_writer_->MaybeWriteData(
      buffer.get(), bytes_written,
      base::BindOnce(&ServiceWorkerNewScriptLoader::OnWriteDataComplete,
                     weak_factory_.GetWeakPtr(), pending_buffer,
                     bytes_written));
}

void ServiceWorkerNewScriptLoader::OnWriteDataComplete(
    scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer,
    size_t bytes_written,
    net::Error error) {
  TRACE_EVENT("ServiceWorker",
              "ServiceWorkerNewScriptLoader::OnWriteDataComplete",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  CHECK_NE(net::ERR_IO_PENDING, error);
  if (error != net::OK) {
    ServiceWorkerMetrics::CountWriteResponseResult(
        ServiceWorkerMetrics::WRITE_DATA_ERROR);
    CommitCompleted(network::URLLoaderCompletionStatus(error),
                    ServiceWorkerConsts::kDatabaseErrorMessage, nullptr);
    return;
  }
  ServiceWorkerMetrics::CountWriteResponseResult(
      ServiceWorkerMetrics::WRITE_OK);

  if (bytes_written == 0) {
    // Zero |bytes_written| with net::OK means that all data has been read from
    // the network and the Mojo data pipe has been closed. Thus we can complete
    // the request if OnComplete() has already been received.
    CHECK(!pending_buffer);
    body_writer_state_ = WriterState::kCompleted;
    if (network_loader_state_ == LoaderState::kCompleted) {
      CommitCompleted(network::URLLoaderCompletionStatus(net::OK),
                      std::string() /* status_message */, nullptr);
    }
    return;
  }

  CHECK(pending_buffer);
  pending_buffer->CompleteRead(bytes_written);
  // Get the consumer handle from a previous read operation if we have one.
  network_consumer_ = pending_buffer->ReleaseHandle();
  network_watcher_.ArmOrNotify();
}

void ServiceWorkerNewScriptLoader::CommitCompleted(
    const network::URLLoaderCompletionStatus& status,
    const std::string& status_message,
    const network::mojom::URLResponseHeadPtr response_head) {
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::CommitCompleted",
              perfetto::Flow::ProcessScoped(
                  request_id_, kServiceWorkerNewScriptLoaderScope));
  net::Error error_code = static_cast<net::Error>(status.error_code);
  std::optional<base::ByteSize> bytes_written;
  std::string sha256_checksum;
  if (error_code == net::OK) {
    CHECK_EQ(LoaderState::kCompleted, network_loader_state_);
    CHECK_EQ(WriterState::kCompleted, header_writer_state_);
    CHECK_EQ(WriterState::kCompleted, body_writer_state_);
    CHECK(cache_writer_->did_replace());
    bytes_written = base::ByteSize(cache_writer_->bytes_written());
    DCHECK_EQ(cache_writer_->checksum_update_timing(),
              ServiceWorkerCacheWriter::ChecksumUpdateTiming::kCacheMismatch);
    sha256_checksum = cache_writer_->GetSha256Checksum();
  } else {
    // When we fail a main script fetch, we do not have a renderer in which to
    // log the failure. We call into devtools with the frame id instead.
    if (requesting_frame_id_ && version_->context()) {
      devtools_instrumentation::OnServiceWorkerMainScriptFetchingFailed(
          requesting_frame_id_, version_->context()->wrapper(),
          version_->version_id(), status_message, status, response_head.get(),
          request_url_);
    } else {
      // AddMessageConsole must be called before notifying that an error
      // occurred because the worker stops soon after receiving the error
      // response.
      // TODO(nhiroki): Consider replacing this hacky way with the new error
      // code handling mechanism in URLLoader.
      version_->AddMessageToConsole(blink::mojom::ConsoleMessageLevel::kError,
                                    status_message);
    }
  }
  version_->script_cache_map()->NotifyFinishedCaching(
      request_url_, bytes_written, sha256_checksum, error_code, status_message);

  client_->OnComplete(status);
  client_producer_.reset();
  client_producer_watcher_.Cancel();

  network_loader_.reset();
  network_consumer_.reset();
  network_watcher_.Cancel();
  cache_writer_.reset();
  network_loader_state_ = LoaderState::kCompleted;
  header_writer_state_ = WriterState::kCompleted;
  body_writer_state_ = WriterState::kCompleted;
}

void ServiceWorkerNewScriptLoader::OnClientWritable(MojoResult result) {
  TRACE_EVENT("ServiceWorker", "ServiceWorkerNewScriptLoader::OnClientWritable",
              perfetto::Flow::ProcessScoped(request_id_,
                                            kServiceWorkerNewScriptLoaderScope),
              "mojo_result", result);
  DCHECK(pending_network_buffer_);
  DCHECK_GT(pending_network_bytes_available_, 0u);

  scoped_refptr<network::MojoToNetPendingBuffer> pending_buffer =
      std::move(pending_network_buffer_);
  uint32_t bytes_available = pending_network_bytes_available_;
  pending_network_bytes_available_ = 0;
  WriteData(std::move(pending_buffer), bytes_available);
}

}  // namespace content
