// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/shared_worker/shared_worker_script_loader.h"

#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/public/browser/resource_context.h"
#include "net/url_request/redirect_util.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "third_party/blink/public/common/service_worker/service_worker_utils.h"

namespace content {

SharedWorkerScriptLoader::SharedWorkerScriptLoader(
    int32_t routing_id,
    int32_t request_id,
    uint32_t options,
    const network::ResourceRequest& resource_request,
    network::mojom::URLLoaderClientPtr client,
    base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
    ResourceContext* resource_context,
    scoped_refptr<network::SharedURLLoaderFactory> default_loader_factory,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
    : routing_id_(routing_id),
      request_id_(request_id),
      options_(options),
      resource_request_(resource_request),
      client_(std::move(client)),
      service_worker_provider_host_(service_worker_provider_host),
      resource_context_(resource_context),
      default_loader_factory_(std::move(default_loader_factory)),
      traffic_annotation_(traffic_annotation),
      url_loader_client_binding_(this),
      weak_factory_(this) {
  DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());

  if (service_worker_provider_host_) {
    service_worker_interceptor_ =
        ServiceWorkerRequestHandler::InitializeForSharedWorker(
            resource_request_, service_worker_provider_host_);
  }

  Start();
}

SharedWorkerScriptLoader::~SharedWorkerScriptLoader() = default;

void SharedWorkerScriptLoader::Start() {
  if (service_worker_interceptor_) {
    service_worker_interceptor_->MaybeCreateLoader(
        resource_request_, resource_context_,
        base::BindOnce(&SharedWorkerScriptLoader::MaybeStartLoader,
                       weak_factory_.GetWeakPtr(),
                       service_worker_interceptor_.get()));
    return;
  }

  LoadFromNetwork();
}

void SharedWorkerScriptLoader::MaybeStartLoader(
    NavigationLoaderInterceptor* interceptor,
    SingleRequestURLLoaderFactory::RequestHandler single_request_handler) {
  if (single_request_handler) {
    // The interceptor elected to handle the request. Use it.
    network::mojom::URLLoaderClientPtr client;
    url_loader_client_binding_.Bind(mojo::MakeRequest(&client));
    url_loader_factory_ = base::MakeRefCounted<SingleRequestURLLoaderFactory>(
        std::move(single_request_handler));
    url_loader_factory_->CreateLoaderAndStart(
        mojo::MakeRequest(&url_loader_), routing_id_, request_id_, options_,
        resource_request_, std::move(client), traffic_annotation_);
    // We continue in URLLoaderClient calls.
    return;
  }

  LoadFromNetwork();
}

void SharedWorkerScriptLoader::LoadFromNetwork() {
  network::mojom::URLLoaderClientPtr client;
  url_loader_client_binding_.Bind(mojo::MakeRequest(&client));
  url_loader_factory_ = default_loader_factory_;
  url_loader_factory_->CreateLoaderAndStart(
      mojo::MakeRequest(&url_loader_), routing_id_, request_id_, options_,
      resource_request_, std::move(client), traffic_annotation_);
  // We continue in URLLoaderClient calls.
}

// URLLoader -------------------------------------------------------------------
// When this class gets a FollowRedirect IPC from the renderer, it restarts with
// the new URL.

void SharedWorkerScriptLoader::FollowRedirect(
    const base::Optional<std::vector<std::string>>&
        to_be_removed_request_headers,
    const base::Optional<net::HttpRequestHeaders>& modified_request_headers) {
  DCHECK(!modified_request_headers.has_value()) << "Redirect with modified "
                                                   "headers was not supported "
                                                   "yet. crbug.com/845683";
  DCHECK(redirect_info_);

  // |should_clear_upload| is unused because there is no body anyway.
  DCHECK(!resource_request_.request_body);
  bool should_clear_upload = false;
  net::RedirectUtil::UpdateHttpRequest(
      resource_request_.url, resource_request_.method, *redirect_info_,
      modified_request_headers, &resource_request_.headers,
      &should_clear_upload);

  resource_request_.url = redirect_info_->new_url;
  resource_request_.method = redirect_info_->new_method;
  resource_request_.site_for_cookies = redirect_info_->new_site_for_cookies;
  resource_request_.referrer = GURL(redirect_info_->new_referrer);
  resource_request_.referrer_policy = redirect_info_->new_referrer_policy;

  // Restart the request.
  url_loader_client_binding_.Unbind();
  redirect_info_.reset();
  Start();
}

void SharedWorkerScriptLoader::ProceedWithResponse() {
  // Only for navigations.
  NOTREACHED();
}

// Below we make a small effort to support the other URLLoader functions by
// forwarding to the current |url_loader_| if any, but don't bother queuing
// state or propagating state to a new URLLoader upon redirect.
void SharedWorkerScriptLoader::SetPriority(net::RequestPriority priority,
                                           int32_t intra_priority_value) {
  if (url_loader_)
    url_loader_->SetPriority(priority, intra_priority_value);
}

void SharedWorkerScriptLoader::PauseReadingBodyFromNet() {
  if (url_loader_)
    url_loader_->PauseReadingBodyFromNet();
}

void SharedWorkerScriptLoader::ResumeReadingBodyFromNet() {
  if (url_loader_)
    url_loader_->ResumeReadingBodyFromNet();
}

// URLLoaderClient ----------------------------------------------------------
// This class forwards any client messages to the outer client in the renderer.
// Additionally, on redirects it saves the redirect info so if the renderer
// calls FollowRedirect(), it can do so.

void SharedWorkerScriptLoader::OnReceiveResponse(
    const network::ResourceResponseHead& response_head) {
  client_->OnReceiveResponse(response_head);
}

void SharedWorkerScriptLoader::OnReceiveRedirect(
    const net::RedirectInfo& redirect_info,
    const network::ResourceResponseHead& response_head) {
  if (--redirect_limit_ == 0) {
    client_->OnComplete(
        network::URLLoaderCompletionStatus(net::ERR_TOO_MANY_REDIRECTS));
    return;
  }

  redirect_info_ = redirect_info;
  client_->OnReceiveRedirect(redirect_info, response_head);
}

void SharedWorkerScriptLoader::OnUploadProgress(
    int64_t current_position,
    int64_t total_size,
    OnUploadProgressCallback ack_callback) {
  client_->OnUploadProgress(current_position, total_size,
                            std::move(ack_callback));
}

void SharedWorkerScriptLoader::OnReceiveCachedMetadata(
    const std::vector<uint8_t>& data) {
  client_->OnReceiveCachedMetadata(data);
}

void SharedWorkerScriptLoader::OnTransferSizeUpdated(
    int32_t transfer_size_diff) {
  client_->OnTransferSizeUpdated(transfer_size_diff);
}

void SharedWorkerScriptLoader::OnStartLoadingResponseBody(
    mojo::ScopedDataPipeConsumerHandle consumer) {
  client_->OnStartLoadingResponseBody(std::move(consumer));
}

void SharedWorkerScriptLoader::OnComplete(
    const network::URLLoaderCompletionStatus& status) {
  if (status.error_code == net::OK)
    service_worker_provider_host_->CompleteSharedWorkerPreparation();
  client_->OnComplete(status);
}

}  // namespace content
