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

#ifndef CONTENT_BROWSER_WORKER_HOST_WORKER_SCRIPT_FETCHER_H_
#define CONTENT_BROWSER_WORKER_HOST_WORKER_SCRIPT_FETCHER_H_

#include <optional>

#include "base/functional/callback.h"
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/common/content_export.h"
#include "content/public/browser/service_worker_client_info.h"
#include "net/storage_access_api/status.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/client_security_state.mojom-forward.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom-forward.h"
#include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
#include "third_party/blink/public/mojom/worker/worker_main_script_load_params.mojom.h"

namespace net {
class IsolationInfo;
class SiteForCookies;
}  // namespace net

namespace network {
struct ResourceRequest;
}  // namespace network

namespace blink {
class PendingURLLoaderFactoryBundle;
class StorageKey;
class ThrottlingURLLoader;
class URLLoaderThrottle;
}  // namespace blink

namespace content {

class DedicatedWorkerHost;
class DevToolsAgentHostImpl;
class RenderFrameHostImpl;
class ServiceWorkerContextWrapper;
class ServiceWorkerMainResourceHandle;
class StoragePartitionImpl;
class WorkerScriptLoaderFactory;

// Contains the result of successful worker script fetch. On fetch failure,
// `std::nullopt` is used instead.
struct CONTENT_EXPORT WorkerScriptFetcherResult final {
  WorkerScriptFetcherResult(
      std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
          subresource_loader_factories,
      blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
      PolicyContainerPolicies policy_container_policies,
      const GURL& final_response_url);
  ~WorkerScriptFetcherResult();

  WorkerScriptFetcherResult(WorkerScriptFetcherResult&& other);
  WorkerScriptFetcherResult& operator=(WorkerScriptFetcherResult&& other);

  // Sent to the renderer process and is to be used to request subresources
  // where applicable. For example, this allows the dedicated worker to load
  // chrome-extension:// URLs which the renderer's default loader factory can't
  // load.
  std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
      subresource_loader_factories;

  // Sent to the renderer process and to be used to load the worker main script
  // pre-requested by the browser process.
  // Always non-null and contains `response_head` and
  // `response_head->parsed_headers`.
  blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params;

  PolicyContainerPolicies policy_container_policies;

  // The script response URL.
  // https://fetch.spec.whatwg.org/#concept-response-url
  GURL final_response_url;
};

// NetworkService (PlzWorker):
// This is an implementation of the URLLoaderClient for web worker's main script
// fetch. The loader and client bounded with this class are to be unbound and
// forwarded to the renderer process on OnReceiveResponse, and the
// resource loader in the renderer process will take them over.
//
// WorkerScriptFetcher deletes itself when the ownership of the loader and
// client is passed to the renderer, or on failure. It lives on the UI
// thread.
// TODO(asamidoi): Remove the manual memory management like `delete this` and
// use `unique_ptr` to create WorkerScriptFetcher in a caller side.
class WorkerScriptFetcher : public network::mojom::URLLoaderClient {
 public:
  // Called with the result of fetching a script upon response received.
  using CompletionCallback =
      base::OnceCallback<void(std::optional<WorkerScriptFetcherResult>)>;

  // Used for specifying how URLLoaderFactoryBundle is used.
  enum class LoaderType { kMainResource, kSubResource };

  // Initiates a browser-side worker script fetch.
  // Creates a `WorkerScriptFetcher` and starts it.
  //
  // Must be called on the UI thread.
  //
  // - `ancestor_render_frame_host` points to the ancestor frame. If
  //   the worker being created is nested, then this is the ancestor of the
  //   creator worker. Otherwise, this is the creator frame. Cannot be nullptr.
  //   For dedicated workers, when the lifetime of the `DedicatedWorkerHost`
  //   does not exactly align with the parents, and they are destroyed
  //   asynchronously via mojo by the time the fetch is about to start,
  //   this method must not be called.
  // - `creator_render_frame_host` points to the creator frame, if any. May
  //   be nullptr if the worker being created is a nested dedicated worker.
  //   Since nested shared workers are not supported, for shared workers
  //   `ancestor_render_frame_host` and `creator_render_frame_host` are always
  //   equal.
  // - `client_security_state` specifies parameters to be passed to the network
  //   service `URLLoaderFactory`, for use when loading the script. It must not
  //   be nullptr.
  // - `callback` will be called with the result on the UI thread.
  static void CreateAndStart(
      int worker_process_id,
      const DedicatedOrSharedWorkerToken& worker_token,
      const GURL& initial_request_url,
      RenderFrameHostImpl& ancestor_render_frame_host,
      RenderFrameHostImpl* creator_render_frame_host,
      DedicatedWorkerHost* creator_worker,
      const net::SiteForCookies& site_for_cookies,
      const url::Origin& request_initiator,
      const blink::StorageKey& request_initiator_storage_key,
      const net::IsolationInfo& trusted_isolation_info,
      network::mojom::ClientSecurityStatePtr client_security_state,
      network::mojom::CredentialsMode credentials_mode,
      blink::mojom::FetchClientSettingsObjectPtr
          outside_fetch_client_settings_object,
      network::mojom::RequestDestination request_destination,
      bool file_url_support,
      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
      ServiceWorkerMainResourceHandle* service_worker_handle,
      scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
      scoped_refptr<network::SharedURLLoaderFactory>
          url_loader_factory_override,
      StoragePartitionImpl* storage_partition,
      const std::string& storage_domain,
      DevToolsAgentHostImpl* devtools_agent_host,
      const base::UnguessableToken& devtools_worker_token,
      bool require_cross_site_request_for_cookies,
      net::StorageAccessApiStatus storage_access_api_status,
      const base::UnguessableToken& worker_network_restrictions_id,
      const base::UnguessableToken& creator_network_restrictions_id,
      std::optional<PolicyContainerPolicies> creator_policies,
      CompletionCallback callback);

  // Creates a loader factory bundle. Must be called on the UI thread. For
  // nested workers, |creator_render_frame_host| can be null.
  static std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
  CreateFactoryBundle(LoaderType loader_type,
                      int worker_process_id,
                      StoragePartitionImpl* storage_partition,
                      const std::string& storage_domain,
                      bool file_support,
                      bool filesystem_url_support,
                      RenderFrameHostImpl* creator_render_frame_host,
                      const blink::StorageKey& request_initiator_storage_key,
                      network::mojom::RequestDestination request_destination);

  // Calculates the final response URL from the redirect chain, URLs fetched by
  // the service worker and the initial request URL. The logic is mostly based
  // on what blink::ResourceResponse::ResponseUrl() does.
  //
  // Exposed for testing.
  CONTENT_EXPORT static GURL DetermineFinalResponseUrl(
      const GURL& initial_request_url,
      blink::mojom::WorkerMainScriptLoadParams* main_script_load_params);

 private:
  // Callback invoked by this instance when the load ends, successfully or not.
  //
  // In case of success:
  //
  // - `main_script_load_params` is not nullptr.
  // - `completion_status` is nullptr.
  //
  // In case of error:
  //
  // - `main_script_load_params` is nullptr.
  // - `completion_status` is not nullptr.
  using CreateAndStartCallback = base::OnceCallback<void(
      blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
      const network::URLLoaderCompletionStatus* completion_status)>;

  WorkerScriptFetcher(
      std::unique_ptr<WorkerScriptLoaderFactory> script_loader_factory,
      std::unique_ptr<network::ResourceRequest> resource_request,
      CreateAndStartCallback callback);

  ~WorkerScriptFetcher() override;

  // Helper for `CreateAndStart()`.
  static void CreateScriptLoader(
      int worker_process_id,
      const DedicatedOrSharedWorkerToken& worker_token,
      const GURL& initial_request_url,
      RenderFrameHostImpl& ancestor_render_frame_host,
      RenderFrameHostImpl* creator_render_frame_host,
      const net::IsolationInfo& trusted_isolation_info,
      network::mojom::ClientSecurityStatePtr client_security_state,
      std::unique_ptr<network::ResourceRequest> resource_request,
      std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
          factory_bundle_for_browser_info,
      std::unique_ptr<blink::PendingURLLoaderFactoryBundle>
          subresource_loader_factories,
      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
      ServiceWorkerMainResourceHandle* service_worker_handle,
      scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
      scoped_refptr<network::SharedURLLoaderFactory>
          url_loader_factory_override,
      DevToolsAgentHostImpl* devtools_agent_host,
      const base::UnguessableToken& devtools_worker_token,
      bool require_cross_site_request_for_cookies,
      const base::UnguessableToken& worker_network_restrictions_id,
      const base::UnguessableToken& creator_network_restrictions_id,
      std::optional<PolicyContainerPolicies> creator_policies,
      WorkerScriptFetcher::CompletionCallback callback);

  void Start(std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles);

  // network::mojom::URLLoaderClient
  void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override;
  void OnReceiveResponse(
      network::mojom::URLResponseHeadPtr head,
      mojo::ScopedDataPipeConsumerHandle body,
      std::optional<mojo_base::BigBuffer> cached_metadata) override;
  void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
                         network::mojom::URLResponseHeadPtr head) override;
  void OnUploadProgress(int64_t current_position,
                        int64_t total_size,
                        OnUploadProgressCallback callback) override;
  void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
  void OnComplete(const network::URLLoaderCompletionStatus& status) override;

  void DidParseHeaders(network::mojom::ParsedHeadersPtr parsed_headers);

  std::unique_ptr<WorkerScriptLoaderFactory> script_loader_factory_;

  // Request ID for a browser-initiated request.
  const int request_id_;

  std::unique_ptr<network::ResourceRequest> resource_request_;
  CreateAndStartCallback callback_;

  // URLLoader instance backed by a request interceptor or the network service.
  std::unique_ptr<blink::ThrottlingURLLoader> url_loader_;

  blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params_;

  std::vector<net::RedirectInfo> redirect_infos_;
  std::vector<network::mojom::URLResponseHeadPtr> redirect_response_heads_;

  base::WeakPtrFactory<WorkerScriptFetcher> weak_factory_{this};
};

}  // namespace content

#endif  // CONTENT_BROWSER_WORKER_HOST_WORKER_SCRIPT_FETCHER_H_
