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

#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "components/services/storage/public/mojom/cache_storage_control.mojom.h"
#include "content/browser/loader/navigation_loader_interceptor.h"
#include "content/browser/loader/response_head_update_params.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/fake_embedded_worker_instance_client.h"
#include "content/browser/service_worker/fake_service_worker.h"
#include "content/browser/service_worker/service_worker_client.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_context_wrapper_test_api.h"
#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_test_utils.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_content_browser_client.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "content/test/fake_network_url_loader_factory.h"
#include "mojo/public/cpp/system/data_pipe_utils.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/single_request_url_loader_factory.h"
#include "services/network/public/cpp/timing_allow_origin_parser.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/service_worker_router_info.mojom-shared.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_client.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
#include "url/origin.h"

namespace content {
namespace service_worker_main_resource_loader_unittest {

class MockSearchPrefetchContentBrowserClient : public TestContentBrowserClient {
 public:
  URLLoaderRequestHandler
  CreateURLLoaderHandlerForServiceWorkerInitiatedNavigationRequest(
      FrameTreeNodeId frame_tree_node_id,
      const network::ResourceRequest& resource_request,
      int64_t navigation_id,
      scoped_refptr<base::SequencedTaskRunner> navigation_response_task_runner)
      override {
    if (handler_) {
      return std::move(handler_);
    }
    return base::NullCallback();
  }

  bool IsServiceWorkerSyntheticResponseAllowed(
      content::BrowserContext* browser_context,
      const GURL& url) override {
    return true;
  }

  void set_handler(URLLoaderRequestHandler handler) {
    handler_ = std::move(handler);
  }

 private:
  URLLoaderRequestHandler handler_;
};

constexpr char kTestCacheName[] = "test cache name";
constexpr char16_t kTestCacheNameU16[] = u"test cache name";

class ScopedOverrideToDisableHighPriorityFetchResponseCallback {
 public:
  ScopedOverrideToDisableHighPriorityFetchResponseCallback() {
    ServiceWorkerFetchDispatcher::
        ForceDisableHighPriorityFetchResponseCallbackForTesting(
            /*force_disable=*/true);
  }
  ScopedOverrideToDisableHighPriorityFetchResponseCallback(
      const ScopedOverrideToDisableHighPriorityFetchResponseCallback&) = delete;
  ScopedOverrideToDisableHighPriorityFetchResponseCallback& operator=(
      const ScopedOverrideToDisableHighPriorityFetchResponseCallback&) = delete;
  ~ScopedOverrideToDisableHighPriorityFetchResponseCallback() {
    ServiceWorkerFetchDispatcher::
        ForceDisableHighPriorityFetchResponseCallbackForTesting(
            /*force_disable=*/false);
  }
};

void ReceiveRequestHandler(
    network::SingleRequestURLLoaderFactory::RequestHandler* out_handler,
    network::SingleRequestURLLoaderFactory::RequestHandler handler) {
  *out_handler = std::move(handler);
}

blink::mojom::FetchAPIResponsePtr OkResponse(
    blink::mojom::SerializedBlobPtr blob_body,
    network::mojom::FetchResponseSource response_source,
    base::Time response_time,
    std::string cache_storage_cache_name) {
  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 200;
  response->status_text = "OK";
  response->response_type = network::mojom::FetchResponseType::kDefault;
  response->response_source = response_source;
  response->response_time = response_time;
  response->cache_storage_cache_name = cache_storage_cache_name;
  response->blob = std::move(blob_body);
  if (response->blob) {
    response->headers.emplace("Content-Length",
                              base::NumberToString(response->blob->size));
  }
  return response;
}

blink::mojom::FetchAPIResponsePtr ErrorResponse() {
  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 0;
  response->response_type = network::mojom::FetchResponseType::kDefault;
  response->error = blink::mojom::ServiceWorkerResponseError::kPromiseRejected;
  return response;
}

blink::mojom::FetchAPIResponsePtr RedirectResponse(
    const std::string& redirect_location_header) {
  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 301;
  response->status_text = "Moved Permanently";
  response->response_type = network::mojom::FetchResponseType::kDefault;
  response->headers["Location"] = redirect_location_header;
  return response;
}

blink::mojom::FetchAPIResponsePtr HeadersResponse(
    const base::flat_map<std::string, std::string>& headers) {
  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 200;
  response->status_text = "OK";
  response->headers.insert(headers.begin(), headers.end());
  return response;
}

// ServiceWorkerMainResourceLoader::RecordTimingMetrics() records the metrics
// only when the consistent high-resolution timer is used among processes.
bool LoaderRecordsTimingMetrics() {
  return base::TimeTicks::IsHighResolution() &&
         base::TimeTicks::IsConsistentAcrossProcesses();
}

// Simulates a service worker handling fetch events. The response can be
// customized via RespondWith* functions.
class FetchEventServiceWorker : public FakeServiceWorker {
 public:
  FetchEventServiceWorker(
      EmbeddedWorkerTestHelper* helper,
      FakeEmbeddedWorkerInstanceClient* embedded_worker_instance_client,
      BrowserTaskEnvironment* task_environment)
      : FakeServiceWorker(helper),
        task_environment_(task_environment),
        embedded_worker_instance_client_(embedded_worker_instance_client) {}

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

  ~FetchEventServiceWorker() override = default;

  // Tells this worker to dispatch a fetch event 1s after the fetch event is
  // received.
  void DispatchAfter1sDelay() {
    response_mode_ = ResponseMode::kDispatchAfter1sDelay;
  }

  // Tells this worker to respond to fetch events with the specified blob.
  void RespondWithBlob(blink::mojom::SerializedBlobPtr blob) {
    response_mode_ = ResponseMode::kBlob;
    blob_body_ = std::move(blob);
  }

  // Tells this worker to respond to fetch events with the specified stream.
  void RespondWithStream(
      mojo::PendingReceiver<blink::mojom::ServiceWorkerStreamCallback>
          callback_receiver,
      mojo::ScopedDataPipeConsumerHandle consumer_handle) {
    response_mode_ = ResponseMode::kStream;
    stream_handle_ = blink::mojom::ServiceWorkerStreamHandle::New();
    stream_handle_->callback_receiver = std::move(callback_receiver);
    stream_handle_->stream = std::move(consumer_handle);
  }

  // Tells this worker to respond to fetch events with network fallback.
  // i.e., simulate the service worker not calling respondWith().
  void RespondWithFallback() {
    response_mode_ = ResponseMode::kFallbackResponse;
  }

  // Tells this worker to respond to fetch events with an error response.
  void RespondWithError() { response_mode_ = ResponseMode::kErrorResponse; }

  // Tells this worker to respond to fetch events with a response containing
  // specific headers.
  void RespondWithHeaders(
      const base::flat_map<std::string, std::string>& headers) {
    response_mode_ = ResponseMode::kHeaders;
    headers_ = headers;
  }

  // Tells this worker to respond to fetch events with the redirect response.
  void RespondWithRedirectResponse(const GURL& new_url) {
    response_mode_ = ResponseMode::kRedirect;
    redirected_url_ = new_url;
  }

  // Tells this worker to simulate failure to dispatch the fetch event to the
  // service worker.
  void FailToDispatchFetchEvent() {
    response_mode_ = ResponseMode::kFailFetchEventDispatch;
  }

  // Tells this worker to simulate "early response", where the respondWith()
  // promise resolves before the waitUntil() promise. In this mode, the
  // helper sets the response mode to "early response", which simulates the
  // promise passed to respondWith() resolving before the waitUntil() promise
  // resolves. In this mode, the helper will respond to fetch events
  // immediately, but will not finish the fetch event until FinishWaitUntil() is
  // called.
  void RespondEarly() { response_mode_ = ResponseMode::kEarlyResponse; }
  void FinishWaitUntil() {
    std::move(finish_callback_)
        .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
    base::RunLoop().RunUntilIdle();
  }

  // Tells this worker to wait for FinishRespondWith() to be called before
  // providing the response to the fetch event.
  void DeferResponse() { response_mode_ = ResponseMode::kDeferredResponse; }
  void FinishRespondWith() {
    response_callback_->OnResponse(
        OkResponse(nullptr /* blob_body */, response_source_, response_time_,
                   cache_storage_cache_name_),
        blink::mojom::ServiceWorkerFetchEventTiming::New(), /*errors=*/nullptr);
    response_callback_.FlushForTesting();
    std::move(finish_callback_)
        .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
  }
  void FinishRespondWithCustomResponse(
      blink::mojom::FetchAPIResponsePtr response) {
    response_callback_->OnResponse(
        std::move(response), blink::mojom::ServiceWorkerFetchEventTiming::New(),
        /*errors=*/nullptr);
    response_callback_.FlushForTesting();
    std::move(finish_callback_)
        .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
  }

  void ReadRequestBody(std::string* out_string) {
    ASSERT_TRUE(request_body_);
    const std::vector<network::DataElement>* elements =
        request_body_->elements();
    // So far this test expects a single bytes element.
    ASSERT_EQ(1u, elements->size());
    const network::DataElement& element = elements->front();
    ASSERT_EQ(network::DataElement::Tag::kBytes, element.type());
    *out_string =
        std::string(element.As<network::DataElementBytes>().AsStringPiece());
  }

  void RunUntilFetchEvent() {
    if (has_received_fetch_event_)
      return;
    base::RunLoop run_loop;
    quit_closure_for_fetch_event_ = run_loop.QuitClosure();
    run_loop.Run();
  }

  void SetResponseSource(network::mojom::FetchResponseSource source) {
    response_source_ = source;
  }

  void SetCacheStorageCacheName(std::string cache_name) {
    cache_storage_cache_name_ = cache_name;
  }

  void SetResponseTime(base::Time time) { response_time_ = time; }

  void WaitForTransferInstalledScript() {
    embedded_worker_instance_client_->WaitForTransferInstalledScript();
  }

 protected:
  void DispatchFetchEventForMainResource(
      blink::mojom::DispatchFetchEventParamsPtr params,
      mojo::PendingRemote<blink::mojom::ServiceWorkerFetchResponseCallback>
          pending_response_callback,
      blink::mojom::ServiceWorker::DispatchFetchEventForMainResourceCallback
          finish_callback) override {
    // Basic checks on DispatchFetchEvent parameters.
    EXPECT_TRUE(params->request->is_main_resource_load);

    has_received_fetch_event_ = true;
    if (params->request->body)
      request_body_ = params->request->body;

    auto timing = blink::mojom::ServiceWorkerFetchEventTiming::New();
    auto now = base::TimeTicks::Now();
    timing->dispatch_event_time = now;
    timing->respond_with_settled_time = now;

    mojo::Remote<blink::mojom::ServiceWorkerFetchResponseCallback>
        response_callback(std::move(pending_response_callback));
    switch (response_mode_) {
      case ResponseMode::kDefault:
        FakeServiceWorker::DispatchFetchEventForMainResource(
            std::move(params), response_callback.Unbind(),
            std::move(finish_callback));
        break;
      case ResponseMode::kDispatchAfter1sDelay:
        task_environment_->AdvanceClock(base::Seconds(1));
        FakeServiceWorker::DispatchFetchEventForMainResource(
            std::move(params), response_callback.Unbind(),
            std::move(finish_callback));
        break;
      case ResponseMode::kBlob:
        response_callback->OnResponse(
            OkResponse(std::move(blob_body_), response_source_, response_time_,
                       cache_storage_cache_name_),
            std::move(timing), /*errors=*/nullptr);
        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
        break;
      case ResponseMode::kStream:
        response_callback->OnResponseStream(
            OkResponse(nullptr /* blob_body */, response_source_,
                       response_time_, cache_storage_cache_name_),
            std::move(stream_handle_), std::move(timing), /*errors=*/nullptr);

        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
        break;
      case ResponseMode::kFallbackResponse:
        response_callback->OnFallback(/*request_body=*/std::nullopt,
                                      std::move(timing), /*errors=*/nullptr);
        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
        break;
      case ResponseMode::kErrorResponse:
        response_callback->OnResponse(ErrorResponse(), std::move(timing),
                                      /*errors=*/nullptr);
        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::REJECTED);
        break;
      case ResponseMode::kFailFetchEventDispatch:
        // Simulate failure by stopping the worker before the event finishes.
        // This causes ServiceWorkerVersion::StartRequest() to call its error
        // callback, which triggers ServiceWorkerMainResourceLoader's dispatch
        // failed behavior.
        embedded_worker_instance_client_->host()->OnStopped();

        // Finish the event by calling |finish_callback|.
        // This is the Mojo callback for
        // blink::mojom::ServiceWorker::DispatchFetchEventForMainResource().
        // If this is not called, Mojo will complain. In production code,
        // ServiceWorkerContextClient would call this when it aborts all
        // callbacks after an unexpected stop.
        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::ABORTED);
        break;
      case ResponseMode::kDeferredResponse:
        finish_callback_ = std::move(finish_callback);
        response_callback_ = std::move(response_callback);
        // Now the caller must call FinishRespondWith() to finish the event.
        break;
      case ResponseMode::kEarlyResponse:
        finish_callback_ = std::move(finish_callback);
        response_callback->OnResponse(
            OkResponse(nullptr /* blob_body */, response_source_,
                       response_time_, cache_storage_cache_name_),
            std::move(timing), /*errors=*/nullptr);
        // Now the caller must call FinishWaitUntil() to finish the event.
        break;
      case ResponseMode::kRedirect:
        response_callback->OnResponse(RedirectResponse(redirected_url_.spec()),
                                      std::move(timing), /*errors=*/nullptr);
        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
        break;
      case ResponseMode::kHeaders:
        response_callback->OnResponse(HeadersResponse(headers_),
                                      std::move(timing), /*errors=*/nullptr);
        std::move(finish_callback)
            .Run(blink::mojom::ServiceWorkerEventStatus::COMPLETED);
        break;
    }

    if (quit_closure_for_fetch_event_)
      std::move(quit_closure_for_fetch_event_).Run();
  }

 private:
  enum class ResponseMode {
    kDefault,
    kDispatchAfter1sDelay,
    kBlob,
    kStream,
    kFallbackResponse,
    kErrorResponse,
    kFailFetchEventDispatch,
    kDeferredResponse,
    kEarlyResponse,
    kRedirect,
    kHeaders
  };

  const raw_ptr<BrowserTaskEnvironment> task_environment_;

  ResponseMode response_mode_ = ResponseMode::kDefault;
  scoped_refptr<network::ResourceRequestBody> request_body_;

  // For ResponseMode::kBlob.
  blink::mojom::SerializedBlobPtr blob_body_;

  // For ResponseMode::kStream.
  blink::mojom::ServiceWorkerStreamHandlePtr stream_handle_;

  // For ResponseMode::kEarlyResponse and kDeferredResponse.
  blink::mojom::ServiceWorker::DispatchFetchEventForMainResourceCallback
      finish_callback_;
  mojo::Remote<blink::mojom::ServiceWorkerFetchResponseCallback>
      response_callback_;

  // For ResponseMode::kRedirect.
  GURL redirected_url_;

  // For ResponseMode::kHeaders
  base::flat_map<std::string, std::string> headers_;

  bool has_received_fetch_event_ = false;
  base::OnceClosure quit_closure_for_fetch_event_;

  const raw_ptr<FakeEmbeddedWorkerInstanceClient>
      embedded_worker_instance_client_;

  network::mojom::FetchResponseSource response_source_ =
      network::mojom::FetchResponseSource::kUnspecified;

  std::string cache_storage_cache_name_;
  base::Time response_time_;
};

// Returns typical response info for a resource load that went through a service
// worker.
network::mojom::URLResponseHeadPtr CreateResponseInfoFromServiceWorker() {
  auto head = network::mojom::URLResponseHead::New();
  head->was_fetched_via_service_worker = true;
  head->url_list_via_service_worker = std::vector<GURL>();
  head->response_type = network::mojom::FetchResponseType::kDefault;
  head->cache_storage_cache_name = std::string();
  head->did_service_worker_navigation_preload = false;
  head->initial_service_worker_status =
      network::mojom::ServiceWorkerStatus::kRunning;
  return head;
}

// Returns router info when request does not match the test routing rules.
network::mojom::ServiceWorkerRouterInfoPtr
CreateExpectedNonMatchingServiceWorkerRouterInfo() {
  auto router_info = network::mojom::ServiceWorkerRouterInfo::New();
  router_info->route_rule_num = 2;
  router_info->evaluation_worker_status =
      network::mojom::ServiceWorkerStatus::kRunning;
  router_info->router_evaluation_time =
      base::ScopedMockElapsedTimersForTest::kMockElapsedTime;
  return router_info;
}

// Returns typical router info when request matches a registered routing rule.
network::mojom::ServiceWorkerRouterInfoPtr
CreateExpectedMatchingServiceWorkerRouterInfo(
    network::mojom::ServiceWorkerRouterSourceType matched_type) {
  auto router_info = network::mojom::ServiceWorkerRouterInfo::New();
  router_info->route_rule_num = 1;
  router_info->evaluation_worker_status =
      network::mojom::ServiceWorkerStatus::kRunning;
  router_info->router_evaluation_time =
      base::ScopedMockElapsedTimersForTest::kMockElapsedTime;
  router_info->rule_id_matched = 1;
  router_info->matched_source_type = matched_type;
  router_info->actual_source_type = matched_type;
  return router_info;
}

const char kHistogramMainResourceFetchEvent[] =
    "ServiceWorker.FetchEvent.MainResource.Status";

// ServiceWorkerMainResourceLoaderTest is for testing the handling of requests
// by a service worker via ServiceWorkerMainResourceLoader.
//
// Of course, no actual service worker runs in the unit test, it is simulated
// via EmbeddedWorkerTestHelper receiving IPC messages from the browser and
// responding as if a service worker is running in the renderer.
class ServiceWorkerMainResourceLoaderTest : public testing::Test {
 public:
  ServiceWorkerMainResourceLoaderTest()
      : task_environment_(BrowserTaskEnvironment::IO_MAINLOOP,
                          base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  ~ServiceWorkerMainResourceLoaderTest() override = default;

  void SetUp() override {
    helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath());

    // Create an active service worker.
    blink::mojom::ServiceWorkerRegistrationOptions options;
    options.scope = GURL("https://example.com/");
    registration_ = CreateNewServiceWorkerRegistration(
        helper_->context()->registry(), options,
        blink::StorageKey::CreateFirstParty(
            url::Origin::Create(options.scope)));
    version_ = CreateNewServiceWorkerVersion(
        helper_->context()->registry(), registration_.get(),
        GURL("https://example.com/service_worker.js"),
        blink::mojom::ScriptType::kClassic);
    std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> records;
    records.push_back(WriteToDiskCacheSync(
        GetStorageControl(), version_->script_url(), {} /* headers */,
        "I'm the body", "I'm the meta data"));
    version_->script_cache_map()->SetResources(records);
    version_->set_fetch_handler_type(
        ServiceWorkerVersion::FetchHandlerType::kNotSkippable);
    version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
    PolicyContainerPolicies policies;
    policies.ip_address_space = network::mojom::IPAddressSpace::kLocal;
    version_->SetPolicyContainerHost(
        base::MakeRefCounted<PolicyContainerHost>(std::move(policies)));
    registration_->SetActiveVersion(version_);

    // Make the registration findable via storage functions.
    registration_->set_last_update_check(base::Time::Now());
    std::optional<blink::ServiceWorkerStatusCode> status;
    base::RunLoop run_loop;
    registry().StoreRegistration(
        registration_.get(), version_.get(),
        ReceiveServiceWorkerStatus(&status, run_loop.QuitClosure()));
    run_loop.Run();
    ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());

    // Set up custom fakes to let tests customize how to respond to fetch
    // events.
    auto* client =
        helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
            helper_.get());
    service_worker_ =
        helper_->AddNewPendingServiceWorker<FetchEventServiceWorker>(
            helper_.get(), client, &task_environment_);

    // Wait for main script response is set to |version| because
    // ServiceWorkerMainResourceLoader needs the main script response to
    // create a response. The main script response is set when the first
    // TransferInstalledScript().
    {
      base::RunLoop loop;
      version_->StartWorker(
          ServiceWorkerMetrics::EventType::UNKNOWN,
          ReceiveServiceWorkerStatus(&status, loop.QuitClosure()));
      loop.Run();
      ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, status.value());
      service_worker_->WaitForTransferInstalledScript();
    }
  }

  ServiceWorkerRegistry& registry() { return helper_->context()->registry(); }
  mojo::Remote<storage::mojom::ServiceWorkerStorageControl>&
  GetStorageControl() {
    return helper_->context()->GetStorageControl();
  }

  void SetUpTestWebContentsAndNavigation(const GURL& url) {
    web_contents_ = WebContentsTester::CreateTestWebContents(
        helper_->browser_context(), nullptr);
    frame_tree_node_id_ =
        web_contents_->GetPrimaryMainFrame()->GetFrameTreeNodeId();

    navigation_simulator_ =
        NavigationSimulator::CreateBrowserInitiated(url, web_contents_.get());
    navigation_simulator_->Start();
  }

  // Starts a request. After calling this, the request is ongoing and the
  // caller can use functions like client_.RunUntilComplete() to wait for
  // completion.
  void StartRequest(std::unique_ptr<network::ResourceRequest> request) {
    // Create a ServiceWorkerClient and simulate what
    // ServiceWorkerControlleeRequestHandler does to assign it a controller.
    if (!service_worker_client_) {
      service_worker_client_ =
          std::make_unique<ScopedServiceWorkerClient>(CreateServiceWorkerClient(
              helper_->context(), request->url,
              /*are_ancestors_secure=*/true, frame_tree_node_id_));
      service_worker_client()->AddMatchingRegistration(registration_.get());
      service_worker_client()->SetControllerRegistration(
          registration_, /*notify_controllerchange=*/false);
      if (network_loader_factory_) {
        service_worker_client()->SetNetworkURLLoaderFactoryForTesting(
            network_loader_factory_);
      }
    }

    // Create a ServiceWorkerMainResourceLoader.
    loader_ = std::make_unique<ServiceWorkerMainResourceLoader>(
        base::BindOnce(&ServiceWorkerMainResourceLoaderTest::Fallback,
                       base::Unretained(this)),
        /*fetch_event_client_id=*/"", service_worker_client()->AsWeakPtr(),
        /*find_registration_start_time=*/base::TimeTicks::Now());

    // Load |request.url|.
    loader_->StartRequest(loader_remote_.BindNewPipeAndPassReceiver(),
                          /*request_id=*/0, /*options=*/0, *request,
                          client_.CreateRemote(),
                          net::MutableNetworkTrafficAnnotationTag());
  }

  // The |fallback_callback| passed to the ServiceWorkerMainResourceLoader in
  // StartRequest().
  network::mojom::URLLoaderFactory* Fallback(ResponseHeadUpdateParams params) {
    did_call_fallback_callback_ = true;
    response_head_update_params_ = std::move(params);
    if (quit_closure_for_fallback_callback_)
      std::move(quit_closure_for_fallback_callback_).Run();
    return fake_url_loader_factory_.get();
  }

  void DidCacheBatchOperation(base::OnceClosure quit_closure,
                              blink::mojom::CacheStorageVerboseErrorPtr error) {
    DCHECK_EQ(error->value, blink::mojom::CacheStorageError::kSuccess);
    std::move(quit_closure).Run();
  }

  void SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType source_type) {
    blink::ServiceWorkerRouterRules rules;
    blink::ServiceWorkerRouterRule rule;
    rule.condition = blink::ServiceWorkerRouterCondition::WithRunningStatus(
        {blink::ServiceWorkerRouterRunningStatusCondition::RunningStatusEnum::
             kRunning});
    blink::ServiceWorkerRouterSource source;
    source.type = source_type;
    switch (source_type) {
      case network::mojom::ServiceWorkerRouterSourceType::kFetchEvent:
        source.fetch_event_source.emplace();
        break;
      case network::mojom::ServiceWorkerRouterSourceType::kNetwork:
        source.network_source.emplace();
        break;
      case network::mojom::ServiceWorkerRouterSourceType::
          kRaceNetworkAndFetchEvent:
        source.race_network_and_fetch_event_source.emplace();
        break;
      case network::mojom::ServiceWorkerRouterSourceType::kCache: {
        blink::ServiceWorkerRouterCacheSource cache_source;
        cache_source.cache_name = kTestCacheName;
        source.cache_source = cache_source;
        break;
      }
      case network::mojom::ServiceWorkerRouterSourceType::
          kRaceNetworkAndCache: {
        source.race_network_and_cache_source.emplace();
        blink::ServiceWorkerRouterCacheSource cache_source;
        cache_source.cache_name = kTestCacheName;
        source.race_network_and_cache_source->cache_source = cache_source;
        break;
      }
    }
    rule.sources.emplace_back(source);
    rules.rules.emplace_back(rule);
    EXPECT_EQ(version_->SetupRouterEvaluator(rules),
              ServiceWorkerRouterEvaluatorErrorEnums::kNoError);
    EXPECT_TRUE(version_->router_evaluator());
  }

  void SetupNetworkResponse() {
    fake_url_loader_factory_ = std::make_unique<FakeNetworkURLLoaderFactory>();
    network_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            fake_url_loader_factory_.get());
  }

  void SetupErrorNetworkResponse() {
    fake_url_loader_factory_ = std::make_unique<FakeNetworkURLLoaderFactory>(
        /* headers=*/std::string(), /* body=*/std::string(),
        /* network_accessed=*/true, net::ERR_CONNECTION_TIMED_OUT);
    network_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            fake_url_loader_factory_.get());
  }

  void SetupNon2xxResponse() {
    fake_url_loader_factory_ = std::make_unique<FakeNetworkURLLoaderFactory>(
        "HTTP/1.1 429 Too Many Requests\nRetry-After: 3600\n\n",
        "Too Many Requests",
        /* network_accessed=*/true, net::OK);
    network_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            fake_url_loader_factory_.get());
  }

  void SetupStoragePartition() {
    ServiceWorkerContextWrapperTestApi(helper_->context_wrapper())
        .set_storage_partition(static_cast<StoragePartitionImpl*>(
            helper_->browser_context()->GetDefaultStoragePartition()));
  }

  std::unique_ptr<network::ResourceRequest> CreateRequestAndSetupCache(
      const base::Time& cache_response_time) {
    auto request = CreateRequest();

    SetupStoragePartition();

    const network::CrossOriginEmbedderPolicy* coep =
        version_->cross_origin_embedder_policy();
    const network::DocumentIsolationPolicy* dip =
        version_->document_isolation_policy();
    mojo::Remote<blink::mojom::CacheStorage> cache_storage;
    auto* storage_partition =
        version_->context()->wrapper()->storage_partition();
    auto* control = storage_partition->GetCacheStorageControl();
    control->AddReceiver(
        *coep, version_->embedded_worker()->GetCoepReporter(), *dip,
        version_->embedded_worker()->GetDipReporter(),
        storage::BucketLocator::ForDefaultBucket(version_->key()),
        storage::mojom::CacheStorageOwner::kCacheAPI,
        cache_storage.BindNewPipeAndPassReceiver());
    mojo::AssociatedRemote<blink::mojom::CacheStorageCache> cache;
    base::RunLoop run_loop;
    cache_storage->Open(
        kTestCacheNameU16, /* trace_id= */ 0,
        base::BindLambdaForTesting([&](blink::mojom::CacheStorage::OpenResult
                                           result) {
          EXPECT_TRUE(result.has_value());

          std::vector<blink::mojom::BatchOperationPtr> operation_ptr_vec;
          operation_ptr_vec.push_back(blink::mojom::BatchOperation::New());
          operation_ptr_vec[0]->operation_type =
              blink::mojom::OperationType::kPut;
          operation_ptr_vec[0]->request = CreateFetchAPIRequest(request.get());
          operation_ptr_vec[0]->response =
              OkResponse(nullptr /* blob_body */,
                         network::mojom::FetchResponseSource::kUnspecified,
                         cache_response_time, kTestCacheName);
          cache.Bind(std::move(result.value()));
          cache->Batch(
              std::move(operation_ptr_vec), /* trace_id= */ 0,
              base::BindOnce(
                  &ServiceWorkerMainResourceLoaderTest::DidCacheBatchOperation,
                  base::Unretained(this), run_loop.QuitClosure()));
        }));
    run_loop.Run();
    return request;
  }

  // Runs until the ServiceWorkerMainResourceLoader created in StartRequest()
  // calls the |fallback_callback| given to it. The argument passed to
  // |fallback_callback| is saved in |reset_subresurce_loader_params_|.
  void RunUntilFallbackCallback() {
    if (did_call_fallback_callback_)
      return;
    base::RunLoop run_loop;
    quit_closure_for_fallback_callback_ = run_loop.QuitClosure();
    run_loop.Run();
  }

  void ExpectRouterInfo(
      const network::mojom::ServiceWorkerRouterInfo& info,
      const network::mojom::ServiceWorkerRouterInfo& expected_info) {
    EXPECT_EQ(expected_info.rule_id_matched, info.rule_id_matched);
    EXPECT_EQ(expected_info.matched_source_type, info.matched_source_type);
    EXPECT_EQ(expected_info.actual_source_type, info.actual_source_type);
    EXPECT_EQ(expected_info.route_rule_num, info.route_rule_num);
    EXPECT_EQ(expected_info.router_evaluation_time,
              info.router_evaluation_time);
    EXPECT_EQ(expected_info.evaluation_worker_status,
              info.evaluation_worker_status);
  }

  void ExpectResponseInfo(
      const network::mojom::URLResponseHead& info,
      const network::mojom::URLResponseHead& expected_info) {
    bool expect_service_worker_timing =
        expected_info.was_fetched_via_service_worker;
    network::mojom::IPAddressSpace expected_client_address_space =
        expected_info.was_fetched_via_service_worker
            ? network::mojom::IPAddressSpace::kLocal
            : network::mojom::IPAddressSpace::kUnknown;
    if (expected_info.service_worker_router_info) {
      if (expected_info.service_worker_router_info->actual_source_type ==
          network::mojom::ServiceWorkerRouterSourceType::kCache) {
        // If the actual router source is Cache, we don't expect service worker
        // timing info.
        expect_service_worker_timing = false;
      }
      if ((expected_info.service_worker_router_info->matched_source_type ==
               network::mojom::ServiceWorkerRouterSourceType::
                   kRaceNetworkAndFetchEvent ||
           expected_info.service_worker_router_info->matched_source_type ==
               network::mojom::ServiceWorkerRouterSourceType::
                   kRaceNetworkAndCache) &&
          expected_info.service_worker_router_info->actual_source_type ==
              network::mojom::ServiceWorkerRouterSourceType::kNetwork) {
        // If the matched router source is race and the actual source is
        // network, we don't expect service worker timing info.
        // `expected_client_address_space` is `kUnknown` as well since this is
        // not override in the ServiceWorkerMainResourceLoader for now.
        //
        // TODO(crbug.com/408309960): Update this to handle the response as if
        // it comes from the fetch event so that every information is propagated
        // correctly.
        expect_service_worker_timing = false;
        expected_client_address_space =
            network::mojom::IPAddressSpace::kUnknown;
      }
    }

    EXPECT_EQ(expected_info.was_fetched_via_service_worker,
              info.was_fetched_via_service_worker);
    EXPECT_EQ(expected_info.url_list_via_service_worker,
              info.url_list_via_service_worker);
    EXPECT_EQ(expected_info.response_type, info.response_type);
    EXPECT_EQ(expected_info.response_time, info.response_time);
    EXPECT_EQ(info.client_address_space, expected_client_address_space);
    EXPECT_EQ(!info.load_timing.service_worker_start_time.is_null(),
              expect_service_worker_timing);
    EXPECT_EQ(!info.load_timing.service_worker_ready_time.is_null(),
              expect_service_worker_timing);
    EXPECT_EQ(!info.load_timing.service_worker_fetch_start.is_null(),
              expect_service_worker_timing);
    EXPECT_EQ(!info.load_timing.service_worker_respond_with_settled.is_null(),
              expect_service_worker_timing);
    EXPECT_LE(info.load_timing.receive_headers_start,
              info.load_timing.receive_headers_end);
    EXPECT_LE(info.load_timing.service_worker_start_time,
              info.load_timing.service_worker_ready_time);
    EXPECT_LE(info.load_timing.service_worker_ready_time,
              info.load_timing.service_worker_fetch_start);
    EXPECT_LE(info.load_timing.service_worker_fetch_start,
              info.load_timing.service_worker_respond_with_settled);
    EXPECT_EQ(expected_info.service_worker_response_source,
              info.service_worker_response_source);
    EXPECT_EQ(expected_info.cache_storage_cache_name,
              info.cache_storage_cache_name);
    EXPECT_EQ(expected_info.did_service_worker_navigation_preload,
              info.did_service_worker_navigation_preload);
    EXPECT_EQ(expected_info.initial_service_worker_status,
              info.initial_service_worker_status);
    if (expected_info.service_worker_router_info) {
      EXPECT_TRUE(info.service_worker_router_info);
      ExpectRouterInfo(*info.service_worker_router_info,
                       *expected_info.service_worker_router_info);
    } else {
      EXPECT_FALSE(info.service_worker_router_info);
    }
  }

  std::unique_ptr<network::ResourceRequest> CreateRequest() {
    std::unique_ptr<network::ResourceRequest> request =
        std::make_unique<network::ResourceRequest>();
    request->url = GURL("https://example.com/");
    request->method = "GET";
    request->mode = network::mojom::RequestMode::kNavigate;
    request->credentials_mode = network::mojom::CredentialsMode::kInclude;
    request->redirect_mode = network::mojom::RedirectMode::kManual;
    request->destination = network::mojom::RequestDestination::kDocument;
    return request;
  }

  // Create FetchAPIRequest for the same request returned from CreateRequest.
  // This is a simplified version of blink::mojom::FetchAPIRequest::From.
  // We use a simplified copy of the code instead of calling
  // FetchAPIRequest::From here because content_test has difficulty linking code
  // from fetch_request_type_converters.cc.
  blink::mojom::FetchAPIRequestPtr CreateFetchAPIRequest(
      const network::ResourceRequest* request) {
    auto fetch_request = blink::mojom::FetchAPIRequest::New();
    fetch_request->is_main_resource_load = true;
    fetch_request->url = request->url;
    fetch_request->method = request->method;
    fetch_request->mode = request->mode;
    fetch_request->credentials_mode = request->credentials_mode;
    fetch_request->redirect_mode = request->redirect_mode;
    fetch_request->destination = request->destination;
    return fetch_request;
  }

  bool HasWorkInBrowser(ServiceWorkerVersion* version) const {
    return version->HasWorkInBrowser();
  }

 protected:
  ServiceWorkerClient* service_worker_client() const {
    return service_worker_client_->get();
  }

  void DeferRequestHandling() {
    fake_url_loader_factory_->DeferHandleRequest();
  }
  void HandleDeferedRequest() {
    fake_url_loader_factory_->HandleDeferredRequest();
  }

  // Declare test_elapsed_timer_ earlier so that it is created before other
  // objects and stays alive after other objects are released.
  base::ScopedMockElapsedTimersForTest test_elapsed_timer_;

  // Declare these first so that they are released last to avoid DanglingPtr
  // issue if service_worker_client_ is released after them.
  std::unique_ptr<FakeNetworkURLLoaderFactory> fake_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_;

  BrowserTaskEnvironment task_environment_;
  std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
  scoped_refptr<ServiceWorkerRegistration> registration_;
  scoped_refptr<ServiceWorkerVersion> version_;
  raw_ptr<FetchEventServiceWorker> service_worker_;
  storage::BlobStorageContext blob_context_;
  network::TestURLLoaderClient client_;
  std::unique_ptr<ServiceWorkerMainResourceLoader> loader_;
  mojo::Remote<network::mojom::URLLoader> loader_remote_;
  std::unique_ptr<ScopedServiceWorkerClient> service_worker_client_;

  bool did_call_fallback_callback_ = false;
  base::OnceClosure quit_closure_for_fallback_callback_;
  ResponseHeadUpdateParams response_head_update_params_;

  RenderViewHostTestEnabler rvh_test_enabler_;
  std::unique_ptr<WebContents> web_contents_;
  std::unique_ptr<NavigationSimulator> navigation_simulator_;
  FrameTreeNodeId frame_tree_node_id_;
};

TEST_F(ServiceWorkerMainResourceLoaderTest, Basic) {
  base::HistogramTester histogram_tester;

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  EXPECT_TRUE(info->was_fetched_via_service_worker);
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);

  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
  }
}

TEST_F(ServiceWorkerMainResourceLoaderTest, NoActiveWorker) {
  base::HistogramTester histogram_tester;

  // Make a container host without a controller.
  service_worker_client_ =
      std::make_unique<ScopedServiceWorkerClient>(CreateServiceWorkerClient(
          helper_->context(), GURL("https://example.com/")));

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code);

  // No fetch event was dispatched.
  histogram_tester.ExpectTotalCount(kHistogramMainResourceFetchEvent, 0);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "StartToForwardServiceWorker",
        0);
  }
}

// Test that the request body is passed to the fetch event.
TEST_F(ServiceWorkerMainResourceLoaderTest, RequestBody) {
  const std::string kData = "hi this is the request body";

  // Create a request with a body.
  auto request_body = base::MakeRefCounted<network::ResourceRequestBody>();
  request_body->AppendCopyOfBytes(base::as_byte_span(kData));
  std::unique_ptr<network::ResourceRequest> request = CreateRequest();
  request->method = "POST";
  request->request_body = request_body;

  // This test doesn't use the response to the fetch event, so just have the
  // service worker do the default simple response.
  StartRequest(std::move(request));
  client_.RunUntilComplete();

  // Verify that the request body was passed to the fetch event.
  std::string body;
  service_worker_->ReadRequestBody(&body);
  EXPECT_EQ(kData, body);
}

TEST_F(ServiceWorkerMainResourceLoaderTest, BlobResponse) {
  base::HistogramTester histogram_tester;

  // Construct the blob to respond with.
  const std::string kResponseBody = "Here is sample text for the blob.";
  auto blob_data = std::make_unique<storage::BlobDataBuilder>("blob-id:myblob");
  blob_data->AppendData(kResponseBody);
  std::unique_ptr<storage::BlobDataHandle> blob_handle =
      blob_context_.AddFinishedBlob(std::move(blob_data));

  auto blob = blink::mojom::SerializedBlob::New();
  blob->uuid = blob_handle->uuid();
  blob->size = blob_handle->size();
  storage::BlobImpl::Create(std::move(blob_handle),
                            blob->blob.InitWithNewPipeAndPassReceiver());
  service_worker_->RespondWithBlob(std::move(blob));
  service_worker_->SetResponseSource(
      network::mojom::FetchResponseSource::kCacheStorage);
  std::string cache_name = "v1";
  service_worker_->SetCacheStorageCacheName(cache_name);
  base::Time response_time = base::Time::Now();
  service_worker_->SetResponseTime(response_time);

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->response_time = response_time;
  expected_info->cache_storage_cache_name = cache_name;
  expected_info->service_worker_response_source =
      network::mojom::FetchResponseSource::kCacheStorage;
  ExpectResponseInfo(*info, *expected_info);
  EXPECT_EQ(33, info->content_length);

  // Test the body.
  std::string body;
  EXPECT_TRUE(client_.response_body().is_valid());
  EXPECT_TRUE(
      mojo::BlockingCopyToString(client_.response_body_release(), &body));
  EXPECT_EQ(kResponseBody, body);
  EXPECT_EQ(net::OK, client_.completion_status().error_code);

  if (LoaderRecordsTimingMetrics()) {
    // Test histogram of reading body.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
  }
}

// Tell the helper to respond with a non-existent Blob.
TEST_F(ServiceWorkerMainResourceLoaderTest, BrokenBlobResponse) {
  base::HistogramTester histogram_tester;

  const std::string kBrokenUUID = "broken_uuid";

  // Create the broken blob.
  std::unique_ptr<storage::BlobDataHandle> blob_handle =
      blob_context_.AddBrokenBlob(kBrokenUUID, "", "",
                                  storage::BlobStatus::ERR_OUT_OF_MEMORY);
  auto blob = blink::mojom::SerializedBlob::New();
  blob->uuid = kBrokenUUID;
  storage::BlobImpl::Create(std::move(blob_handle),
                            blob->blob.InitWithNewPipeAndPassReceiver());
  service_worker_->RespondWithBlob(std::move(blob));

  // Perform the request.
  StartRequest(CreateRequest());

  // We should get a valid response once the headers arrive.
  client_.RunUntilResponseReceived();
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  // However, since the blob is broken we should get an error while transferring
  // the body.
  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_BLOB_OUT_OF_MEMORY,
            client_.completion_status().error_code);

  if (LoaderRecordsTimingMetrics()) {
    // Timing histograms shouldn't be recorded on broken response.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "StartToForwardServiceWorker",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
  }
}

TEST_F(ServiceWorkerMainResourceLoaderTest, StreamResponse) {
  base::HistogramTester histogram_tester;

  // Construct the Stream to respond with.
  const std::string_view kResponseBody = "Here is sample text for the Stream.";
  mojo::Remote<blink::mojom::ServiceWorkerStreamCallback> stream_callback;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle),
            MOJO_RESULT_OK);
  service_worker_->RespondWithStream(
      stream_callback.BindNewPipeAndPassReceiver(), std::move(consumer_handle));

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilResponseReceived();

  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  EXPECT_FALSE(version_->HasNoWork());

  // Write the body stream.
  size_t actually_written_bytes = 0;
  MojoResult mojo_result = producer_handle->WriteData(
      base::as_byte_span(kResponseBody), MOJO_WRITE_DATA_FLAG_NONE,
      actually_written_bytes);
  ASSERT_EQ(MOJO_RESULT_OK, mojo_result);
  EXPECT_EQ(kResponseBody.size(), actually_written_bytes);
  stream_callback->OnCompleted();
  producer_handle.reset();

  client_.RunUntilComplete();
  EXPECT_EQ(net::OK, client_.completion_status().error_code);

  // Test the body.
  std::string response;
  EXPECT_TRUE(client_.response_body().is_valid());
  EXPECT_TRUE(
      mojo::BlockingCopyToString(client_.response_body_release(), &response));
  EXPECT_EQ(kResponseBody, response);

  if (LoaderRecordsTimingMetrics()) {
    // Test histogram of reading body.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
  }
}

// Test when a stream response body is aborted.
TEST_F(ServiceWorkerMainResourceLoaderTest, StreamResponse_Abort) {
  base::HistogramTester histogram_tester;

  // Construct the Stream to respond with.
  const std::string_view kResponseBody = "Here is sample text for the Stream.";
  mojo::Remote<blink::mojom::ServiceWorkerStreamCallback> stream_callback;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle),
            MOJO_RESULT_OK);
  service_worker_->RespondWithStream(
      stream_callback.BindNewPipeAndPassReceiver(), std::move(consumer_handle));

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilResponseReceived();

  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  // Start writing the body stream, then abort before finishing.
  size_t actually_written_bytes = 0;
  MojoResult mojo_result = producer_handle->WriteData(
      base::as_byte_span(kResponseBody), MOJO_WRITE_DATA_FLAG_NONE,
      actually_written_bytes);
  ASSERT_EQ(MOJO_RESULT_OK, mojo_result);
  EXPECT_EQ(kResponseBody.size(), actually_written_bytes);
  stream_callback->OnAborted();
  producer_handle.reset();

  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);

  // Test the body.
  std::string response;
  EXPECT_TRUE(client_.response_body().is_valid());
  EXPECT_TRUE(
      mojo::BlockingCopyToString(client_.response_body_release(), &response));
  EXPECT_EQ(kResponseBody, response);

  if (LoaderRecordsTimingMetrics()) {
    // Timing histograms shouldn't be recorded on abort.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "StartToForwardServiceWorker",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
  }
}

// Test when the loader is cancelled while a stream response is being written.
TEST_F(ServiceWorkerMainResourceLoaderTest, StreamResponseAndCancel) {
  base::HistogramTester histogram_tester;

  // Construct the Stream to respond with.
  const std::string_view kResponseBody = "Here is sample text for the Stream.";
  mojo::Remote<blink::mojom::ServiceWorkerStreamCallback> stream_callback;
  mojo::ScopedDataPipeProducerHandle producer_handle;
  mojo::ScopedDataPipeConsumerHandle consumer_handle;
  ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle),
            MOJO_RESULT_OK);
  service_worker_->RespondWithStream(
      stream_callback.BindNewPipeAndPassReceiver(), std::move(consumer_handle));

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilResponseReceived();

  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  // Start writing the body stream, then break the Mojo connection to the loader
  // before finishing.
  size_t actually_written_bytes = 0;
  MojoResult mojo_result = producer_handle->WriteData(
      base::as_byte_span(kResponseBody), MOJO_WRITE_DATA_FLAG_NONE,
      actually_written_bytes);
  ASSERT_EQ(MOJO_RESULT_OK, mojo_result);
  EXPECT_EQ(kResponseBody.size(), actually_written_bytes);
  EXPECT_TRUE(producer_handle.is_valid());
  loader_remote_.reset();
  base::RunLoop().RunUntilIdle();

  // Although ServiceWorkerMainResourceLoader resets its URLLoaderClient pointer
  // on connection error, the URLLoaderClient still exists. In this test, it is
  // |client_| which owns the data pipe, so it's still valid to write data to
  // it.
  mojo_result = producer_handle->WriteData(base::as_byte_span(kResponseBody),
                                           MOJO_WRITE_DATA_FLAG_NONE,
                                           actually_written_bytes);
  // TODO(falken): This should probably be an error.
  EXPECT_EQ(MOJO_RESULT_OK, mojo_result);

  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);

  if (LoaderRecordsTimingMetrics()) {
    // Timing histograms shouldn't be recorded on cancel.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "StartToForwardServiceWorker",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
  }
}

// Test when the service worker responds with network fallback.
// i.e., does not call respondWith().
TEST_F(ServiceWorkerMainResourceLoaderTest, FallbackResponse) {
  base::HistogramTester histogram_tester;
  service_worker_->RespondWithFallback();

  // Perform the request.
  StartRequest(CreateRequest());

  // The fallback callback should be called.
  RunUntilFallbackCallback();

  // The request should not be handled by the loader, but it shouldn't be a
  // failure.
  EXPECT_TRUE(service_worker_client()->controller());
  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    // Test histogram of network fallback.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        1);
  }
}

// Test when the service worker rejects the FetchEvent.
TEST_F(ServiceWorkerMainResourceLoaderTest, ErrorResponse) {
  base::HistogramTester histogram_tester;
  service_worker_->RespondWithError();

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_FAILED, client_.completion_status().error_code);

  // Event dispatch still succeeded.
  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    // Timing UMAs shouldn't be recorded when we receive an error response.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "StartToForwardServiceWorker",
        0);
  }
}

// Test when dispatching the fetch event to the service worker failed.
TEST_F(ServiceWorkerMainResourceLoaderTest, FailFetchDispatch) {
  // This test simulates failure to dispatch the fetch event to the
  // service worker by calling
  // `service_worker_->FailToDispatchFetchEvent()`. But without
  // disabling high priority fetch response callback, request processing
  // comes earlier, and doesn't fail to fetch dispatch.  This test is
  // still valid after introducing HighPriorityFetchResponseCallback.
  ScopedOverrideToDisableHighPriorityFetchResponseCallback
      disable_high_priority_fetch_response_callback;

  base::HistogramTester histogram_tester;
  service_worker_->FailToDispatchFetchEvent();

  // Perform the request.
  StartRequest(CreateRequest());

  // The fallback callback should be called.
  RunUntilFallbackCallback();
  EXPECT_FALSE(service_worker_client()->controller());

  histogram_tester.ExpectUniqueSample(
      kHistogramMainResourceFetchEvent,
      blink::ServiceWorkerStatusCode::kErrorFailed, 1);
  if (LoaderRecordsTimingMetrics()) {
    // Timing UMAs shouldn't be recorded when failed to dispatch an event.
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "StartToForwardServiceWorker",
        0);
  }
}

// Test when the respondWith() promise resolves before the waitUntil() promise
// resolves. The response should be received before the event finishes.
TEST_F(ServiceWorkerMainResourceLoaderTest, EarlyResponse) {
  service_worker_->RespondEarly();

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  // Although the response was already received, the event remains outstanding
  // until waitUntil() resolves.
  EXPECT_TRUE(HasWorkInBrowser(version_.get()));
  service_worker_->FinishWaitUntil();
  EXPECT_FALSE(HasWorkInBrowser(version_.get()));
}

// Test responding to the fetch event with a redirect response.
TEST_F(ServiceWorkerMainResourceLoaderTest, Redirect) {
  base::HistogramTester histogram_tester;
  GURL new_url("https://example.com/redirected");
  service_worker_->RespondWithRedirectResponse(new_url);

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilRedirectReceived();

  auto& info = client_.response_head();
  EXPECT_EQ(301, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());

  const net::RedirectInfo& redirect_info = client_.redirect_info();
  EXPECT_EQ(301, redirect_info.status_code);
  EXPECT_EQ("GET", redirect_info.new_method);
  EXPECT_EQ(new_url, redirect_info.new_url);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
}

TEST_F(ServiceWorkerMainResourceLoaderTest, Lifetime) {
  StartRequest(CreateRequest());
  base::WeakPtr<ServiceWorkerMainResourceLoader> loader = loader_->AsWeakPtr();
  ASSERT_TRUE(loader);

  client_.RunUntilComplete();
  EXPECT_TRUE(loader);

  // Even after calling DetachedFromRequest(), |loader_| should be alive until
  // the Mojo connection to the loader is disconnected.
  loader_.release()->DetachedFromRequest();
  EXPECT_TRUE(loader);

  // When the remote for |loader_| is disconnected, its weak pointers (|loader|)
  // are invalidated.
  loader_remote_.reset();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(loader);

  // |loader_| is deleted here. LSan test will alert if it leaks.
}

TEST_F(ServiceWorkerMainResourceLoaderTest, Lifetime_RaceNetworkRequest) {
  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);
  service_worker_->DeferResponse();
  SetupNetworkResponse();

  StartRequest(CreateRequest());
  base::WeakPtr<ServiceWorkerMainResourceLoader> loader = loader_->AsWeakPtr();
  ASSERT_TRUE(loader);

  // The network request wins, but the fetch event is still in flight.
  client_.RunUntilComplete();
  EXPECT_TRUE(loader);

  // Even after calling DetachedFromRequest(), |loader_| should be alive until
  // the fetch event completes.
  loader_.release()->DetachedFromRequest();
  EXPECT_TRUE(loader);

  // Finish the fetch event. This should trigger the deletion of |loader_|.
  service_worker_->FinishRespondWith();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(loader);
}

// Tests that the loader handles being detached while running the fallback
// callback. This can happen if the navigation is cancelled while the fallback
// callback is on the stack.
TEST_F(ServiceWorkerMainResourceLoaderTest, DetachedDuringFallbackCallback) {
  service_worker_->RespondWithFallback();
  SetupNetworkResponse();

  std::unique_ptr<network::ResourceRequest> request = CreateRequest();
  service_worker_client_ = std::make_unique<ScopedServiceWorkerClient>(
      CreateServiceWorkerClient(helper_->context(), request->url));
  service_worker_client()->AddMatchingRegistration(registration_.get());
  service_worker_client()->SetControllerRegistration(
      registration_, /*notify_controllerchange=*/false);

  base::RunLoop run_loop;
  loader_ = std::make_unique<ServiceWorkerMainResourceLoader>(
      base::BindLambdaForTesting(
          [&](ResponseHeadUpdateParams) -> network::mojom::URLLoaderFactory* {
            // Simulate the owning interceptor being torn down while the
            // fallback callback is running.
            loader_.release()->DetachedFromRequest();
            run_loop.Quit();
            return fake_url_loader_factory_.get();
          }),
      /*fetch_event_client_id=*/"", service_worker_client()->AsWeakPtr(),
      /*find_registration_start_time=*/base::TimeTicks::Now());
  base::WeakPtr<ServiceWorkerMainResourceLoader> loader = loader_->AsWeakPtr();
  loader_->StartRequest(loader_remote_.BindNewPipeAndPassReceiver(),
                        /*request_id=*/0, /*options=*/0, *request,
                        client_.CreateRemote(),
                        net::MutableNetworkTrafficAnnotationTag());
  run_loop.Run();

  // The loader should have been deleted.
  EXPECT_FALSE(loader);
}

TEST_F(ServiceWorkerMainResourceLoaderTest, ConnectionErrorDuringFetchEvent) {
  service_worker_->DeferResponse();
  StartRequest(CreateRequest());

  // Wait for the fetch event to be dispatched.
  service_worker_->RunUntilFetchEvent();

  // Break the Mojo connection. The loader should return an aborted status.
  loader_remote_.reset();
  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);

  // The loader is still alive. Finish the fetch event. It shouldn't crash or
  // call any callbacks on |client_|, which would throw an error.
  service_worker_->FinishRespondWith();

  // There's no event to wait for, so just pump the message loop and the test
  // passes if there is no error or crash.
  base::RunLoop().RunUntilIdle();
}

TEST_F(ServiceWorkerMainResourceLoaderTest, CancelNavigationDuringFetchEvent) {
  // This test simulates failure by resetting ServiceWorkerClient.  But without
  // disabling HighPriorityFetchResponseCallback,
  // `Release()` comes later than
  // request processing, and doesn't cancel navigation during the fetch
  // event.  This test is still valid after introducing
  // HighPriorityFetchResponseCallback.
  ScopedOverrideToDisableHighPriorityFetchResponseCallback
      disable_high_priority_fetch_response_callback;

  StartRequest(CreateRequest());

  // Delete the container host during the request. The load should abort without
  // crashing.
  service_worker_client_.reset();
  base::RunLoop().RunUntilIdle();

  client_.RunUntilComplete();
  EXPECT_EQ(net::ERR_ABORTED, client_.completion_status().error_code);
}

TEST_F(ServiceWorkerMainResourceLoaderTest, TimingInfo) {
  service_worker_->DispatchAfter1sDelay();

  // Perform the request.
  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  // The response header's timing is recorded appropriately.
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  ExpectResponseInfo(*info, *CreateResponseInfoFromServiceWorker());
  EXPECT_EQ(base::Seconds(1), info->load_timing.service_worker_ready_time -
                                  info->load_timing.service_worker_start_time);
  EXPECT_EQ(base::Seconds(1), info->load_timing.service_worker_fetch_start -
                                  info->load_timing.service_worker_start_time);
  EXPECT_EQ(base::Seconds(1),
            info->load_timing.service_worker_respond_with_settled -
                info->load_timing.service_worker_start_time);
}

TEST_F(ServiceWorkerMainResourceLoaderTest, FencedFrameNavigationPreload) {
  registration_->EnableNavigationPreload(true);

  std::unique_ptr<network::ResourceRequest> request = CreateRequest();
  SetUpTestWebContentsAndNavigation(request->url);
  request->destination = network::mojom::RequestDestination::kFencedframe;

  // Perform the request.
  StartRequest(std::move(request));
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  const auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->did_service_worker_navigation_preload = true;
  ExpectResponseInfo(*info, *expected_info);
}

// Similar to Basic test setup, but with non matching static routing rules.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingNoMatch) {
  base::HistogramTester histogram_tester;

  // Setup 2 non matching static routing rules.
  {
    blink::ServiceWorkerRouterRules rules;
    blink::ServiceWorkerRouterRule rule;
    rule.condition = blink::ServiceWorkerRouterCondition::WithRunningStatus(
        {blink::ServiceWorkerRouterRunningStatusCondition::RunningStatusEnum::
             kNotRunning});
    blink::ServiceWorkerRouterSource source;
    source.type = network::mojom::ServiceWorkerRouterSourceType::kNetwork;
    source.network_source.emplace();
    rule.sources.emplace_back(source);
    rules.rules.emplace_back(rule);
    blink::ServiceWorkerRouterRule rule2;
    blink::ServiceWorkerRouterRequestCondition request_condition;
    request_condition.mode = network::mojom::RequestMode::kCors;
    rule2.sources.emplace_back(source);
    rules.rules.emplace_back(rule);
    EXPECT_EQ(version_->SetupRouterEvaluator(rules),
              ServiceWorkerRouterEvaluatorErrorEnums::kNoError);
    EXPECT_TRUE(version_->router_evaluator());
  }

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->service_worker_router_info =
      CreateExpectedNonMatchingServiceWorkerRouterInfo();
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);

  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
  }
}

// Similar to Basic test setup, but with matching static fetch routing rule.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingFetch) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kFetchEvent);

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->service_worker_router_info =
      CreateExpectedMatchingServiceWorkerRouterInfo(
          network::mojom::ServiceWorkerRouterSourceType::kFetchEvent);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);

  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
  }
}

// Similar to Basic test setup, but with matching network static routing rule.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingNetwork) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kNetwork);

  SetupNetworkResponse();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  // FakeNetworkURLLoaderFactory doesn't set any of the following.
  EXPECT_TRUE(info->load_timing.receive_headers_start.is_null());
  EXPECT_TRUE(info->load_timing.receive_headers_end.is_null());
  EXPECT_EQ(info->client_address_space,
            network::mojom::IPAddressSpace::kUnknown);
  EXPECT_FALSE(info->was_fetched_via_service_worker);

  // The test FakeNetworkURLLoaderFactory doesn't copy head updates over to
  // final response as NavigationURLLoaderImpl::OnReceiveResponse does.
  // Therefore, verify router info by checking router info seen in fallback
  // call.
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kNetwork);
  ExpectRouterInfo(*response_head_update_params_.router_info,
                   *expected_router_info);

  // As the request goes to network directly, there should be no fetch event
  // dispatched and it is not reported as fallback to network.
  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 0);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Similar to Basic test setup, but with matching race static routing rule and
// network wins.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingRaceNetworkWin) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);

  service_worker_->DeferResponse();
  SetupNetworkResponse();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  // The loader is still alive. Finish the fetch event. It shouldn't crash or
  // call any callbacks on |client_|, which would throw an error.
  service_worker_->FinishRespondWith();
  // There's no event to wait for, just pump the message loop and the test
  // passes if there is no error or crash.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->was_fetched_via_service_worker = true;
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kNetwork;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  // As the fetch event has not completed before request completes, there should
  // be no fetch event related histograms logged.
  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 0);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
    // Logged as part of RecordTimingMetricsForRaceNetworkRequestCase().
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
  }
}

// Matching race static routing rule and the network request wins, but the
// response is not 2xx.
TEST_F(ServiceWorkerMainResourceLoaderTest,
       StaticRoutingRaceNetworkWinWithNon2xx) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);

  SetupNon2xxResponse();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  // There's no event to wait for, just pump the message loop and the test
  // passes if there is no error or crash.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kFetchEvent;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Similar to Basic test setup, but with matching race static routing rule and
// fetch event wins.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingRaceFetchWin) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);

  SetupErrorNetworkResponse();

  // Defer the race network request processing to receive the fetch handler
  // response first.
  DeferRequestHandling();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  // After receiving the fetch handler response, resume the network request
  // processing.
  HandleDeferedRequest();

  // There's no event to wait for, just pump the message loop and the test
  // passes if there is no error or crash.
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kFetchEvent;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        1);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Similar to Basic test setup, but with matching cache static routing rule and
// a matching entry in cache.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingCache) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kCache);

  base::Time response_time = base::Time::Now();
  auto request = CreateRequestAndSetupCache(response_time);

  StartRequest(std::move(request));
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  EXPECT_FALSE(info->load_timing.service_worker_cache_lookup_start.is_null());
  EXPECT_EQ(info->client_address_space, network::mojom::IPAddressSpace::kLocal);
  EXPECT_TRUE(info->was_fetched_via_service_worker);
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->service_worker_response_source =
      network::mojom::FetchResponseSource::kCacheStorage;
  expected_info->response_time = response_time;
  expected_info->cache_storage_cache_name = kTestCacheName;
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kCache);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kCache;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Reproduces the crash reported in Issue 507149743.
// When a response is served from CacheStorage via Static Router,
// parsed_headers is currently reset to nullptr. If the request is cross-origin,
// ServiceWorkerMainResourceLoader::StartResponse attempts to perform a TAO
// check using parsed_headers, leading to a null pointer dereference.
TEST_F(ServiceWorkerMainResourceLoaderTest,
       StaticRoutingCache_TimingAllowOriginCrash) {
  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kCache);

  base::Time response_time = base::Time::Now();
  auto request = CreateRequestAndSetupCache(response_time);
  // Set request_initiator to a different origin to trigger TAO check.
  request->request_initiator =
      url::Origin::Create(GURL("https://other.example.com"));

  StartRequest(std::move(request));
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
}

// Similar to Basic test setup, but with matching cache static routing rule and
// no entry in cache.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingCacheMiss) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kCache);

  // setup storage partition so that we don't error out unexpectedly in
  // ServiceWorkerCacheStorageMatcher::Run.
  SetupStoragePartition();

  // With no entry in cache, it will fallback to network, so setup network
  // response.
  SetupNetworkResponse();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  // The fallback FakeNetworkURLLoaderFactory doesn't set any of the following.
  EXPECT_TRUE(info->load_timing.receive_headers_start.is_null());
  EXPECT_TRUE(info->load_timing.receive_headers_end.is_null());
  EXPECT_EQ(info->client_address_space,
            network::mojom::IPAddressSpace::kUnknown);
  EXPECT_FALSE(info->was_fetched_via_service_worker);

  // The test FakeNetworkURLLoaderFactory doesn't copy head updates over to
  // final response as NavigationURLLoaderImpl::OnReceiveResponse does.
  // Therefore, verify router info by checking router info seen in fallback
  // call.
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kCache);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kNetwork;
  ExpectRouterInfo(*response_head_update_params_.router_info,
                   *expected_router_info);

  // Cache matching miss is treated as normal fallback and not as kErrorFailed.
  // And as response is not completed by service worker fetch or fallback from
  // fetch, no related histograms are logged.
  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Similar to Basic test setup, but with matching cache static routing rule and
// had unexpected failure during cache match.
TEST_F(ServiceWorkerMainResourceLoaderTest, StaticRoutingCacheFailure) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kCache);

  // Not setting up storage partition, which is unexpected and cache matching
  // will finish with kErrorFailed.

  // With no entry in cache, it will fallback to network, so setup network
  // response.
  SetupNetworkResponse();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  // The fallback FakeNetworkURLLoaderFactory doesn't set any of the following.
  EXPECT_TRUE(info->load_timing.receive_headers_start.is_null());
  EXPECT_TRUE(info->load_timing.receive_headers_end.is_null());
  EXPECT_EQ(info->client_address_space,
            network::mojom::IPAddressSpace::kUnknown);
  EXPECT_FALSE(info->was_fetched_via_service_worker);

  // NotifyControllerLost should be called and the Fallback callback should be
  // called with ResponseHeadUpdateParams().
  EXPECT_FALSE(service_worker_client()->controller());
  EXPECT_TRUE(did_call_fallback_callback_);
  EXPECT_FALSE(response_head_update_params_.router_info);
  EXPECT_FALSE(response_head_update_params_.initial_service_worker_status);

  // Cache matching failed and logged as kErrorFailed.
  // And as response is not completed by service worker fetch or fallback from
  // fetch, no related histograms are logged.
  histogram_tester.ExpectUniqueSample(
      kHistogramMainResourceFetchEvent,
      blink::ServiceWorkerStatusCode::kErrorFailed, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Similar to Basic test setup, but with matching race-network-and-cache static
// routing rule and network wins.
TEST_F(ServiceWorkerMainResourceLoaderTest,
       StaticRoutingRaceNetworkAndCacheNetworkWin) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache);

  SetupStoragePartition();

  SetupNetworkResponse();

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->was_fetched_via_service_worker = true;
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kNetwork;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 0);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
  }
}

// Similar to Basic test setup, but with matching race-network-and-cache static
// routing rule and the network wins, but the response is not 2xx.
TEST_F(ServiceWorkerMainResourceLoaderTest,
       StaticRoutingRaceNetWorkAndCacheNetworkWinWithNon2xx) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache);

  SetupNon2xxResponse();

  base::Time response_time = base::Time::Now();
  auto request = CreateRequestAndSetupCache(response_time);

  StartRequest(CreateRequest());
  client_.RunUntilComplete();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  EXPECT_FALSE(info->load_timing.service_worker_cache_lookup_start.is_null());
  EXPECT_EQ(info->client_address_space, network::mojom::IPAddressSpace::kLocal);
  EXPECT_TRUE(info->was_fetched_via_service_worker);
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->service_worker_response_source =
      network::mojom::FetchResponseSource::kCacheStorage;
  expected_info->response_time = response_time;
  expected_info->cache_storage_cache_name = kTestCacheName;
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kCache;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

// Similar to Basic test setup, but with matching race-network-and-cache static
// routing rule and cache wins.
TEST_F(ServiceWorkerMainResourceLoaderTest,
       StaticRoutingRaceNetWorkAndCacheCacheWin) {
  base::HistogramTester histogram_tester;

  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache);

  SetupErrorNetworkResponse();

  // Defer the race network request processing to receive the cache
  // response first.
  DeferRequestHandling();

  base::Time response_time = base::Time::Now();
  auto request = CreateRequestAndSetupCache(response_time);

  StartRequest(std::move(request));
  client_.RunUntilComplete();

  // After receiving the cache response, resume the network request
  // processing.
  HandleDeferedRequest();

  EXPECT_EQ(net::OK, client_.completion_status().error_code);
  auto& info = client_.response_head();
  EXPECT_EQ(200, info->headers->response_code());
  EXPECT_FALSE(info->load_timing.receive_headers_start.is_null());
  EXPECT_FALSE(info->load_timing.receive_headers_end.is_null());
  EXPECT_FALSE(info->load_timing.service_worker_cache_lookup_start.is_null());
  EXPECT_EQ(info->client_address_space, network::mojom::IPAddressSpace::kLocal);
  EXPECT_TRUE(info->was_fetched_via_service_worker);
  auto expected_info = CreateResponseInfoFromServiceWorker();
  expected_info->service_worker_response_source =
      network::mojom::FetchResponseSource::kCacheStorage;
  expected_info->response_time = response_time;
  expected_info->cache_storage_cache_name = kTestCacheName;
  auto expected_router_info = CreateExpectedMatchingServiceWorkerRouterInfo(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache);
  expected_router_info->actual_source_type =
      network::mojom::ServiceWorkerRouterSourceType::kCache;
  expected_info->service_worker_router_info = std::move(expected_router_info);
  ExpectResponseInfo(*info, *expected_info);

  histogram_tester.ExpectUniqueSample(kHistogramMainResourceFetchEvent,
                                      blink::ServiceWorkerStatusCode::kOk, 1);
  if (LoaderRecordsTimingMetrics()) {
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "ResponseReceivedToCompleted2",
        0);
    histogram_tester.ExpectTotalCount(
        "ServiceWorker.LoadTiming.MainFrame.MainResource."
        "FetchHandlerEndToFallbackNetwork",
        0);
  }
}

TEST_F(ServiceWorkerMainResourceLoaderTest, SearchPrefetchHitInSyntheticResponse) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeatureWithParameters(
      blink::features::kServiceWorkerSyntheticResponse,
      {{"bypass_subresource", "true"}});

  MockSearchPrefetchContentBrowserClient mock_browser_client;
  ContentBrowserClient* old_browser_client =
      SetBrowserClientForTesting(&mock_browser_client);

  bool handler_called = false;
  mock_browser_client.set_handler(base::BindLambdaForTesting(
      [&](const network::ResourceRequest& resource_request,
          mojo::PendingReceiver<network::mojom::URLLoader> url_loader_receiver,
          mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
        handler_called = true;
      }));

  std::unique_ptr<network::ResourceRequest> request = CreateRequest();
  SetUpTestWebContentsAndNavigation(request->url);
  request->is_outermost_main_frame = true;

  StartRequest(std::move(request));

  EXPECT_TRUE(handler_called);
  EXPECT_EQ(service_worker_client()->fetch_handler_bypass_option(),
            blink::mojom::ServiceWorkerFetchHandlerBypassOption::
                kSyntheticResponse);

  SetBrowserClientForTesting(old_browser_client);
}

// Tests that when the Service Worker fetch handler responds faster than the
// network request with a 204 No Content, and the network request subsequently
// receives a 302 redirect, the loader is leaked (default behavior on current
// branch).
TEST_F(ServiceWorkerMainResourceLoaderTest,
       Lifetime_RaceNetworkRequest_Redirect_SW_Wins_204_Leak) {
  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);
  service_worker_->DeferResponse();

  // Variables to keep the receiver and client alive to simulate the race
  // request.
  mojo::PendingReceiver<network::mojom::URLLoader> keep_alive_loader_receiver;
  mojo::Remote<network::mojom::URLLoaderClient> race_client_remote;

  // Setup SingleRequestURLLoaderFactory to simulate a 302 redirect.
  auto handler = base::BindLambdaForTesting(
      [&](const network::ResourceRequest& resource_request,
          mojo::PendingReceiver<network::mojom::URLLoader> receiver,
          mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
        // Keep the receiver alive to prevent premature connection error on the
        // loader side.
        keep_alive_loader_receiver = std::move(receiver);

        // Bind the client to a Remote to monitor its connection status.
        race_client_remote.Bind(std::move(client));

        net::RedirectInfo redirect_info;
        redirect_info.status_code = 302;
        redirect_info.new_url = GURL("https://example.com/redirected");

        auto response = network::mojom::URLResponseHead::New();
        response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
            "HTTP/1.1 302 Found\nLocation: https://example.com/redirected\n\n");

        // Send the redirect.
        race_client_remote->OnReceiveRedirect(redirect_info,
                                              std::move(response));
      });

  network_loader_factory_ =
      base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
          std::move(handler));

  StartRequest(CreateRequest());
  base::WeakPtr<ServiceWorkerMainResourceLoader> loader = loader_->AsWeakPtr();
  ASSERT_TRUE(loader);

  // Wait for the fetch event to be dispatched to the Service Worker.
  service_worker_->RunUntilFetchEvent();

  // SW responds with 204 No Content.
  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 204;
  response->status_text = "No Content";
  response->response_type = network::mojom::FetchResponseType::kDefault;

  service_worker_->FinishRespondWithCustomResponse(std::move(response));
  client_.RunUntilComplete();
  EXPECT_TRUE(loader);

  // The navigation is completed and the loader is detached.
  loader_.release()->DetachedFromRequest();

  // Reset the remote to disconnect the Mojo pipe, which triggers
  // DeleteIfNeeded() on connection error.
  loader_remote_.reset();

  // Let the network request (302 redirect) run.
  base::RunLoop().RunUntilIdle();

  // With the fix, ShouldDelayDeletion() correctly returns false on redirect,
  // allowing the loader to be cleanly destroyed and the Mojo pipe to be
  // disconnected.
  EXPECT_FALSE(loader);
  EXPECT_FALSE(race_client_remote.is_connected());
}

TEST_F(ServiceWorkerMainResourceLoaderTest,
       Lifetime_RaceNetworkRequest_Redirect_SW_Wins_204_Leak_ReverseOrder) {
  SetupStaticRoutingRules(
      network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndFetchEvent);
  service_worker_->DeferResponse();

  mojo::PendingReceiver<network::mojom::URLLoader> keep_alive_loader_receiver;
  mojo::Remote<network::mojom::URLLoaderClient> race_client_remote;

  net::RedirectInfo redirect_info;
  redirect_info.status_code = 302;
  redirect_info.new_url = GURL("https://example.com/redirected");
  auto response_head = network::mojom::URLResponseHead::New();
  response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      "HTTP/1.1 302 Found\nLocation: https://example.com/redirected\n\n");

  auto handler = base::BindLambdaForTesting(
      [&](const network::ResourceRequest& resource_request,
          mojo::PendingReceiver<network::mojom::URLLoader> receiver,
          mojo::PendingRemote<network::mojom::URLLoaderClient> client) {
        keep_alive_loader_receiver = std::move(receiver);
        race_client_remote.Bind(std::move(client));
      });

  network_loader_factory_ =
      base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
          std::move(handler));

  StartRequest(CreateRequest());
  base::WeakPtr<ServiceWorkerMainResourceLoader> loader = loader_->AsWeakPtr();
  ASSERT_TRUE(loader);

  service_worker_->RunUntilFetchEvent();

  // 1. SW responds with 204 first.
  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 204;
  response->status_text = "No Content";
  response->response_type = network::mojom::FetchResponseType::kDefault;

  service_worker_->FinishRespondWithCustomResponse(std::move(response));
  client_.RunUntilComplete();
  EXPECT_TRUE(loader);

  // 2. The navigation is completed and the loader is detached.
  loader_.release()->DetachedFromRequest();
  loader_remote_.reset();

  // The loader should still be alive because the redirect has not arrived yet.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(loader);

  // 3. Now, late-arrive the 302 redirect on the race network request.
  race_client_remote->OnReceiveRedirect(redirect_info,
                                        std::move(response_head));

  // This should trigger the completion callback -> DeleteIfNeeded() and cleanly
  // destroy the loader.
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(loader);
  EXPECT_FALSE(race_client_remote.is_connected());
}

struct TimingAllowTestCase {
  std::optional<std::string> request_initiator;
  network::mojom::FetchResponseType response_type;
  std::optional<std::string> timing_allow_origin;
  bool expected_timing_allow_passed;
};

class ServiceWorkerMainResourceLoaderTimingAllowTest
    : public ServiceWorkerMainResourceLoaderTest,
      public testing::WithParamInterface<TimingAllowTestCase> {};

TEST_P(ServiceWorkerMainResourceLoaderTimingAllowTest, CheckTimingAllowPassed) {
  const auto& test_case = GetParam();
  service_worker_->DeferResponse();
  auto request = CreateRequest();
  if (test_case.request_initiator) {
    request->request_initiator =
        url::Origin::Create(GURL(*test_case.request_initiator));
  } else {
    request->request_initiator = std::nullopt;
  }
  StartRequest(std::move(request));
  service_worker_->RunUntilFetchEvent();

  auto response = blink::mojom::FetchAPIResponse::New();
  response->status_code = 200;
  response->status_text = "OK";
  response->response_type = test_case.response_type;
  if (test_case.timing_allow_origin) {
    response->parsed_headers = network::mojom::ParsedHeaders::New();
    response->parsed_headers->timing_allow_origin =
        network::ParseTimingAllowOrigin(*test_case.timing_allow_origin);
  }

  service_worker_->FinishRespondWithCustomResponse(std::move(response));
  client_.RunUntilComplete();
  EXPECT_EQ(test_case.expected_timing_allow_passed,
            client_.response_head()->timing_allow_passed);
}

INSTANTIATE_TEST_SUITE_P(
    ServiceWorkerMainResourceLoaderTest,
    ServiceWorkerMainResourceLoaderTimingAllowTest,
    testing::Values(
        // 1. Same-origin request initiator (https://example.com):
        // Same-origin (kBasic) and synthetic (kDefault) responses pass.
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kBasic,
                            std::nullopt, true},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kDefault,
                            std::nullopt, true},
        // Filtered responses without Timing-Allow-Origin header fail.
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kCors,
                            std::nullopt, false},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kError,
                            std::nullopt, false},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kOpaque,
                            std::nullopt, false},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kOpaqueRedirect,
                            std::nullopt, false},
        // Filtered responses with valid Timing-Allow-Origin pass.
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kCors, "*",
                            true},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kOpaque, "*",
                            true},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kCors,
                            "https://example.com", true},
        // Filtered responses with mismatching Timing-Allow-Origin fail.
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kCors,
                            "https://other.example.com", false},
        TimingAllowTestCase{"https://example.com",
                            network::mojom::FetchResponseType::kOpaque,
                            "https://other.example.com", false},

        // 2. Cross-origin request initiator (https://other.example.com):
        // Same-origin responses without TAO fail because initiator is
        // cross-origin.
        TimingAllowTestCase{"https://other.example.com",
                            network::mojom::FetchResponseType::kBasic,
                            std::nullopt, false},
        TimingAllowTestCase{"https://other.example.com",
                            network::mojom::FetchResponseType::kDefault,
                            std::nullopt, false},
        // With matching TAO, they pass.
        TimingAllowTestCase{"https://other.example.com",
                            network::mojom::FetchResponseType::kBasic,
                            "https://other.example.com", true},
        TimingAllowTestCase{"https://other.example.com",
                            network::mojom::FetchResponseType::kDefault,
                            "https://other.example.com", true},
        TimingAllowTestCase{"https://other.example.com",
                            network::mojom::FetchResponseType::kCors,
                            "https://other.example.com", true},
        TimingAllowTestCase{"https://other.example.com",
                            network::mojom::FetchResponseType::kCors,
                            std::nullopt, false},

        // 3. No request initiator:
        TimingAllowTestCase{std::nullopt,
                            network::mojom::FetchResponseType::kBasic,
                            std::nullopt, false},
        TimingAllowTestCase{std::nullopt,
                            network::mojom::FetchResponseType::kCors, "*",
                            false}));

}  // namespace service_worker_main_resource_loader_unittest
}  // namespace content
