// Copyright 2024 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_client.h"

#include <set>
#include <variant>

#include "base/check_is_test.h"
#include "base/containers/adapters.h"
#include "base/debug/alias.h"
#include "base/debug/crash_logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/types/optional_util.h"
#include "base/uuid.h"
#include "content/browser/loader/navigation_url_loader_impl.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/security/cpsp/child_process_security_policy_impl.h"
#include "content/browser/service_worker/service_worker_container_host.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_security_utils.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/worker_host/dedicated_worker_host.h"
#include "content/browser/worker_host/dedicated_worker_service_impl.h"
#include "content/browser/worker_host/shared_worker_host.h"
#include "content/browser/worker_host/shared_worker_service_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_util.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/single_request_url_loader_factory.h"
#include "services/network/public/cpp/url_loader_factory_builder.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "third_party/blink/public/common/service_worker/service_worker_scope_match.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_running_status_callback.mojom.h"

namespace content {

namespace {

void RunCallbacks(
    std::vector<ServiceWorkerClient::ExecutionReadyCallback> callbacks) {
  for (auto& callback : callbacks) {
    std::move(callback).Run();
  }
}

std::optional<int64_t> GetOngoingNavigationId(
    FrameTreeNodeId frame_tree_node_id) {
  if (auto* frame_tree_node =
          FrameTreeNode::GloballyFindByID(frame_tree_node_id)) {
    if (auto* navigation_request = frame_tree_node->navigation_request()) {
      return navigation_request->GetNavigationId();
    }
  }
  return std::nullopt;
}

}  // namespace

// RAII helper class for keeping track of versions waiting for an update hint
// from the renderer.
//
// This class is move-only.
class ServiceWorkerClient::PendingUpdateVersion {
 public:
  explicit PendingUpdateVersion(scoped_refptr<ServiceWorkerVersion> version)
      : version_(std::move(version)) {
    version_->IncrementPendingUpdateHintCount();
  }

  PendingUpdateVersion(PendingUpdateVersion&& other) {
    version_ = std::move(other.version_);
  }

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

  ~PendingUpdateVersion() {
    if (version_) {
      version_->DecrementPendingUpdateHintCount();
    }
  }

  PendingUpdateVersion& operator=(PendingUpdateVersion&& other) {
    version_ = std::move(other.version_);
    return *this;
  }

  // Needed for base::flat_set.
  bool operator<(const PendingUpdateVersion& other) const {
    return version_ < other.version_;
  }

 private:
  scoped_refptr<ServiceWorkerVersion> version_;
};

// To realize the running status condition in the ServiceWorker static routing
// API, the latest ServiceWorker running status is notified to all renderers
// that execute ServiceWorker subresource loaders and controlled by the
// ServiceWorker.
//
// This is an observer to receive the ServiceWorker running status change,
// and make the update notified to all the renderers via mojo IPC.
//
// See:
// https://w3c.github.io/ServiceWorker/#dom-routercondition-runningstatus
class ServiceWorkerClient::ServiceWorkerRunningStatusObserver final
    : public ServiceWorkerVersion::Observer {
 public:
  void OnRunningStateChanged(ServiceWorkerVersion* version) override {
    Notify(version->running_status());
  }
  void Notify(blink::EmbeddedWorkerStatus status) {
    for (const auto& callback : callbacks_) {
      callback->OnStatusChanged(status);
    }
  }
  void AddCallback(
      mojo::PendingRemote<blink::mojom::ServiceWorkerRunningStatusCallback>
          callback) {
    callbacks_.Add(std::move(callback));
  }

 private:
  mojo::RemoteSet<blink::mojom::ServiceWorkerRunningStatusCallback> callbacks_;
};

ServiceWorkerClient::ServiceWorkerClient(
    base::WeakPtr<ServiceWorkerContextCore> context,
    bool is_parent_frame_secure,
    FrameTreeNodeId ongoing_navigation_frame_tree_node_id)
    : context_(std::move(context)),
      owner_(context_->service_worker_client_owner()),
      create_time_(base::TimeTicks::Now()),
      client_uuid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
      is_parent_frame_secure_(is_parent_frame_secure),
      is_initiated_by_prefetch_(false),
      client_info_(ServiceWorkerClientInfo()),
      ongoing_navigation_frame_tree_node_id_(
          ongoing_navigation_frame_tree_node_id) {
  DCHECK(context_);
}

ServiceWorkerClient::ServiceWorkerClient(
    base::WeakPtr<ServiceWorkerContextCore> context,
    bool is_parent_frame_secure,
    scoped_refptr<network::SharedURLLoaderFactory>
        network_url_loader_factory_for_prefetch)
    : context_(std::move(context)),
      owner_(context_->service_worker_client_owner()),
      create_time_(base::TimeTicks::Now()),
      client_uuid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
      is_parent_frame_secure_(is_parent_frame_secure),
      is_initiated_by_prefetch_(true),
      client_info_(ServiceWorkerClientInfo()),
      network_url_loader_factory_for_prefetch_(
          std::move(network_url_loader_factory_for_prefetch)) {
  DCHECK(context_);
}

ServiceWorkerClient::ServiceWorkerClient(
    base::WeakPtr<ServiceWorkerContextCore> context,
    ChildProcessId process_id,
    ServiceWorkerClientInfo client_info)
    : context_(std::move(context)),
      owner_(context_->service_worker_client_owner()),
      create_time_(base::TimeTicks::Now()),
      client_uuid_(base::Uuid::GenerateRandomV4().AsLowercaseString()),
      is_parent_frame_secure_(true),
      is_initiated_by_prefetch_(false),
      client_info_(client_info),
      process_id_for_worker_client_(process_id) {
  DCHECK(context_);
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(process_id_for_worker_client_);
}

ServiceWorkerClient::~ServiceWorkerClient() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (destruction_callback_for_testing_) {
    CHECK_IS_TEST();
    std::move(destruction_callback_for_testing_).Run();
  }

  if (IsContainerForWindowClient()) {
    auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
    if (rfh) {
      rfh->RemoveServiceWorkerClient(client_uuid());
    }
  }

  if (controller_) {
    controller_->Uncontrol(client_uuid());

    if (running_status_observer_) {
      controller_->RemoveObserver(running_status_observer_.get());
      running_status_observer_.reset();
    }
  }

  // Remove |this| as an observer of ServiceWorkerRegistrations.
  // TODO(falken): Use base::ScopedObservation instead of this explicit call.
  controller_.reset();
  controller_registration_.reset();

  // Ensure callbacks awaiting execution ready are notified.
  RunExecutionReadyCallbacks();

  RemoveAllMatchingRegistrations();
}

void ServiceWorkerClient::HintToUpdateServiceWorker() {
  // The destructors notify the ServiceWorkerVersions to update.
  versions_to_update_.clear();
}

void ServiceWorkerClient::EnsureFileAccess(
    const std::vector<base::FilePath>& file_paths,
    blink::mojom::ServiceWorkerContainerHost::EnsureFileAccessCallback
        callback) {
  ServiceWorkerVersion* version =
      controller_registration_ ? controller_registration_->active_version()
                               : nullptr;

  // The controller might have legitimately been lost due to
  // NotifyControllerLost(), so don't ReportBadMessage() here.
  if (version) {
    ChildProcessId controller_process_id =
        version->embedded_worker()->process_id();
    ChildProcessId process_id = GetProcessId();

    ChildProcessSecurityPolicyImpl* policy =
        ChildProcessSecurityPolicyImpl::GetInstance();
    for (const auto& file : file_paths) {
      if (!policy->CanReadFile(process_id, file)) {
        mojo::ReportBadMessage(
            "The renderer doesn't have access to the file "
            "but it tried to grant access to the controller.");
        return;
      }

      if (!policy->CanReadFile(controller_process_id, file)) {
        policy->GrantReadFile(controller_process_id, file);
      }
    }
  }

  std::move(callback).Run();
}

void ServiceWorkerClient::OnExecutionReady() {
  // Since `OnExecutionReady()` is a part of `ServiceWorkerContainerHost`,
  // this method is called only if `is_container_ready()` is true.
  CHECK(is_container_ready());

  if (is_execution_ready()) {
    mojo::ReportBadMessage("SWPH_OER_ALREADY_READY");
    return;
  }

  // The controller was sent on navigation commit but we must send it again here
  // because 1) the controller might have changed since navigation commit due to
  // skipWaiting(), and 2) the UseCounter might have changed since navigation
  // commit, in such cases the updated information was prevented being sent due
  // to false is_execution_ready().
  // TODO(leonhsl): Create some layout tests covering the above case 1), in
  // which case we may also need to set |notify_controllerchange| correctly.
  container_host()->SendSetController(false /* notify_controllerchange */);

  SetExecutionReady();
}

void ServiceWorkerClient::OnVersionAttributesChanged(
    ServiceWorkerRegistration* registration,
    blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (container_host()) {
    container_host()->OnVersionAttributesChanged(registration,
                                                 std::move(changed_mask));
  }
}

void ServiceWorkerClient::OnRegistrationFailed(
    ServiceWorkerRegistration* registration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  RemoveMatchingRegistration(registration);
}

void ServiceWorkerClient::OnRegistrationFinishedUninstalling(
    ServiceWorkerRegistration* registration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  RemoveMatchingRegistration(registration);
}

void ServiceWorkerClient::OnSkippedWaiting(
    ServiceWorkerRegistration* registration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (controller_registration_ != registration) {
    return;
  }

#if DCHECK_IS_ON()
  DCHECK(controller_);
  ServiceWorkerVersion* active = controller_registration_->active_version();
  DCHECK(active);
  DCHECK_NE(active, controller_.get());
  DCHECK_EQ(active->status(), ServiceWorkerVersion::ACTIVATING);
#endif  // DCHECK_IS_ON()

  if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
    // This ServiceWorkerContainerHost is evicted from BackForwardCache in
    // |ActivateWaitingVersion|, but not deleted yet. This can happen because
    // asynchronous eviction and |OnSkippedWaiting| are in the same task.
    // The controller does not have to be updated because |this| will be evicted
    // from BackForwardCache.
    // TODO(yuzus): Wire registration with ServiceWorkerContainerHost so that we
    // can check on the caller side.
    return;
  }

  UpdateController(true /* notify_controllerchange */);
}

void ServiceWorkerClient::AddMatchingRegistration(
    ServiceWorkerRegistration* registration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(blink::ServiceWorkerScopeMatches(registration->scope(),
                                          GetUrlForScopeMatch()));
  DCHECK(registration->key() == key());
  if (!IsEligibleForServiceWorkerController()) {
    return;
  }
  size_t key = registration->scope().spec().size();
  auto [it, inserted] = matching_registrations_.try_emplace(key, registration);
  if (!inserted) {
    return;
  }
  registration->AddListener(this);

  if (container_host()) {
    container_host()->ReturnRegistrationForReadyIfNeeded();
  }
}

void ServiceWorkerClient::RemoveMatchingRegistration(
    ServiceWorkerRegistration* registration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_NE(controller_registration_, registration);
  CHECK(IsMatchingRegistration(registration));

  registration->RemoveListener(this);
  size_t key = registration->scope().spec().size();
  matching_registrations_.erase(key);
}

ServiceWorkerRegistration* ServiceWorkerClient::MatchRegistration() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  for (const auto& registration : base::Reversed(matching_registrations_)) {
    if (registration.second->is_uninstalled()) {
      continue;
    }
    if (registration.second->is_uninstalling()) {
      return nullptr;
    }
    return registration.second.get();
  }
  return nullptr;
}

void ServiceWorkerClient::AddServiceWorkerToUpdate(
    scoped_refptr<ServiceWorkerVersion> version) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // This is only called for windows now, but it should be called for all
  // clients someday.
  DCHECK(IsContainerForWindowClient());

  versions_to_update_.emplace(std::move(version));
}

void ServiceWorkerClient::CountFeature(blink::mojom::WebFeature feature) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // `container_` can be used only if ServiceWorkerContainerInfoForClient has
  // been passed to the renderer process. Otherwise, the method call will crash
  // inside the mojo library (See crbug.com/40918057).
  if (!is_container_ready()) {
    buffered_used_features_.insert(feature);
    return;
  }

  // And only when loading finished so the controller is really settled.
  if (!is_execution_ready()) {
    buffered_used_features_.insert(feature);
    return;
  }

  container_host()->CountFeature(feature);
}

void ServiceWorkerClient::NotifyControllerLost() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
    // The controller was unregistered, which usually does not happen while it
    // has controllees. Since the document is in the back/forward cache, it does
    // not count as a controllee. However, this means if it were to be restored,
    // the page would be in an unexpected state, so evict the bfcache entry.
    EvictFromBackForwardCache(BackForwardCacheMetrics::NotRestoredReason::
                                  kServiceWorkerUnregistration);
  }

  SetControllerRegistration(nullptr, true /* notify_controllerchange */);
}

void ServiceWorkerClient::ClaimedByRegistration(
    scoped_refptr<ServiceWorkerRegistration> registration) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(registration->active_version());
  DCHECK(is_execution_ready());

  // TODO(falken): This should just early return, or DCHECK. claim() should have
  // no effect on a page that's already using the registration.
  if (registration == controller_registration_) {
    UpdateController(true /* notify_controllerchange */);
    return;
  }

  SetControllerRegistration(registration, true /* notify_controllerchange */);
}

blink::mojom::ServiceWorkerClientType ServiceWorkerClient::GetClientType()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::visit(
      absl::Overload(
          [](GlobalRenderFrameHostId render_frame_host_id) {
            return blink::mojom::ServiceWorkerClientType::kWindow;
          },
          [](blink::DedicatedWorkerToken dedicated_worker_token) {
            return blink::mojom::ServiceWorkerClientType::kDedicatedWorker;
          },
          [](blink::SharedWorkerToken shared_worker_token) {
            return blink::mojom::ServiceWorkerClientType::kSharedWorker;
          }),
      client_info_);
}

bool ServiceWorkerClient::IsContainerForWindowClient() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::holds_alternative<GlobalRenderFrameHostId>(client_info_);
}

bool ServiceWorkerClient::IsContainerForWorkerClient() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return std::holds_alternative<blink::DedicatedWorkerToken>(client_info_) ||
         std::holds_alternative<blink::SharedWorkerToken>(client_info_);
}

ServiceWorkerClientInfo ServiceWorkerClient::GetServiceWorkerClientInfo()
    const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return client_info_;
}

blink::mojom::ServiceWorkerContainerInfoForClientPtr
ServiceWorkerClient::CommitResponse(
    base::PassKey<ScopedServiceWorkerClient>,
    std::optional<GlobalRenderFrameHostId> rfh_id,
    const PolicyContainerPolicies& policy_container_policies,
    mojo::PendingRemote<network::mojom::CrossOriginEmbedderPolicyReporter>
        coep_reporter,
    mojo::PendingRemote<network::mojom::DocumentIsolationPolicyReporter>
        dip_reporter,
    ukm::SourceId ukm_source_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK_EQ(client_phase_, ClientPhase::kInitial);

  if (IsContainerForWindowClient()) {
    CHECK(coep_reporter);
    CHECK(rfh_id);
    ongoing_navigation_frame_tree_node_id_ = FrameTreeNodeId();
    client_info_ = *rfh_id;

    if (controller_) {
      controller_->UpdateForegroundPriority();
    }

    auto* rfh = RenderFrameHostImpl::FromID(*rfh_id);
    // `rfh` may be null in tests (but it should not happen in production).
    if (rfh) {
      rfh->AddServiceWorkerClient(client_uuid(),
                                  weak_ptr_factory_.GetWeakPtr());
      // A window client hosted by a privileged WebContents (see //chrome's
      // PrivilegedWebContents) that forbids service worker control is
      // permanently ineligible to be controlled by a service worker. This is
      // determined here, at response commit, because that is the first point
      // where the client's RenderFrameHost (and thus its WebContents) is known.
      // Interception of the navigation itself is prevented separately, at
      // navigation start (NavigationRequest sets skip_service_worker); this bit
      // additionally blocks the non-interception control paths (ClaimClients,
      // registration/controller inheritance), so a privileged client can never
      // become controlled.
      if (WebContentsImpl* web_contents =
              WebContentsImpl::FromRenderFrameHostImpl(rfh)) {
        const std::optional<WebContents::PrivilegedParams>& privileged_params =
            web_contents->privileged_params();
        if (privileged_params &&
            privileged_params->disallow_service_worker_control) {
          SetDisallowsServiceWorkerControl();
        }
      }
    }
  }

  CHECK(!container_host_);

  auto container_info =
      blink::mojom::ServiceWorkerContainerInfoForClient::New();
  container_host_ = std::make_unique<ServiceWorkerContainerHostForClient>(
      base::PassKey<ServiceWorkerClient>(), AsWeakPtr(), container_info,
      policy_container_policies, std::move(coep_reporter),
      std::move(dip_reporter), std::move(ukm_source_id));

  // `network_url_loader_factory_for_prefetch_` is no longer used after commit.
  network_url_loader_factory_for_prefetch_.reset();

  TransitionToClientPhase(ClientPhase::kResponseCommitted);

  return container_info;
}

void ServiceWorkerClient::OnEndNavigationCommit() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsContainerForWindowClient());

  DCHECK(!navigation_commit_ended_);
  navigation_commit_ended_ = true;

  if (controller_) {
    controller_->OnControlleeNavigationCommitted(client_uuid_,
                                                 GetRenderFrameHostId());
  }
}

void ServiceWorkerClient::UpdateUrlsInternal(
    const GURL& creation_url,
    const std::optional<url::Origin>& top_frame_origin,
    const blink::StorageKey& storage_key) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  const GURL url = creation_url.is_valid()
                       ? net::SimplifyUrlForRequest(creation_url)
                       : creation_url;
  GURL previous_url = url_;
  creation_url_ = creation_url;
  // The url_ needs the URL fragment removed, but the creation URL needs to be
  // the original URL including the fragment.
  url_ = url;
  top_frame_origin_ = top_frame_origin;
  key_ = storage_key;
  service_worker_security_utils::CheckOnUpdateUrls(GetUrlForScopeMatch(), key_);

  if (previous_url != url) {
    // Revoke the token on URL change since any service worker holding the token
    // may no longer be the potential controller of this frame and shouldn't
    // have the power to display SSL dialogs for it.
    if (IsContainerForWindowClient()) {
      fetch_request_window_id_ = base::UnguessableToken::Create();
    }
  }

  // Update client id on cross origin redirects. This corresponds to the HTML
  // standard's "process a navigation fetch" algorithm's step for discarding
  // |reservedEnvironment|.
  // https://html.spec.whatwg.org/multipage/browsing-the-web.html#process-a-navigate-fetch
  // "If |reservedEnvironment| is not null and |currentURL|'s origin is not the
  // same as |reservedEnvironment|'s creation URL's origin, then:
  //    1. Run the environment discarding steps for |reservedEnvironment|.
  //    2. Set |reservedEnvironment| to null."
  if (previous_url.is_valid() && !url::IsSameOriginWith(previous_url, url)) {
    // Remove old controller since we know the controller is definitely
    // changed. We need to remove |this| from |controller_|'s controllee before
    // updating UUID since ServiceWorkerVersion has a map from uuid to provider
    // hosts.
    SetControllerRegistration(nullptr, false /* notify_controllerchange */);

    // Set UUID to the new one.
    std::string previous_client_uuid = client_uuid_;
    client_uuid_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
    owner_->UpdateServiceWorkerClientClientID(previous_client_uuid,
                                              client_uuid_);
  }

  SyncMatchingRegistrations();
}

namespace {

// Attempt to get the storage key from |RenderFrameHostImpl|. This correctly
// accounts for extension URLs. The absence of this logic was a potential cause
// for https://crbug.com/1346450.
std::optional<blink::StorageKey> GetStorageKeyFromRenderFrameHost(
    FrameTreeNodeId frame_tree_node_id,
    const url::Origin& origin,
    const base::UnguessableToken* nonce) {
  FrameTreeNode* frame_tree_node =
      FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (!frame_tree_node) {
    return std::nullopt;
  }
  RenderFrameHostImpl* frame_host = frame_tree_node->current_frame_host();
  if (!frame_host) {
    return std::nullopt;
  }

  return frame_host->CalculateStorageKey(origin, nonce);
}

// For dedicated/shared worker cases, if a storage key is returned, it will have
// its origin replaced by |origin|. This would mean that the origin of the
// WorkerHost and the origin as used by the service worker code don't match,
// however in cases where these wouldn't match the load will be aborted later
// anyway.
std::optional<blink::StorageKey> GetStorageKeyFromDedicatedWorkerHost(
    content::StoragePartition* storage_partition,
    blink::DedicatedWorkerToken dedicated_worker_token,
    const url::Origin& origin) {
  auto* worker_service = static_cast<DedicatedWorkerServiceImpl*>(
      storage_partition->GetDedicatedWorkerService());
  auto* worker_host =
      worker_service->GetDedicatedWorkerHostFromToken(dedicated_worker_token);
  if (worker_host) {
    return worker_host->GetWorkerStorageKey().WithOrigin(origin);
  }
  return std::nullopt;
}

std::optional<blink::StorageKey> GetStorageKeyFromSharedWorkerHost(
    content::StoragePartition* storage_partition,
    blink::SharedWorkerToken shared_worker_token,
    const url::Origin& origin) {
  auto* worker_service = static_cast<SharedWorkerServiceImpl*>(
      storage_partition->GetSharedWorkerService());
  auto* worker_host =
      worker_service->GetSharedWorkerHostFromToken(shared_worker_token);
  if (worker_host) {
    return worker_host->GetWorkerStorageKey().WithOrigin(origin);
  }
  return std::nullopt;
}

}  // namespace

blink::StorageKey ServiceWorkerClient::CalculateStorageKeyForUpdateUrls(
    const GURL& url,
    const net::IsolationInfo& isolation_info_from_handle) const {
  CHECK(!is_response_committed());

  const url::Origin origin = url::Origin::Create(url);

  const std::optional<blink::StorageKey> storage_key = std::visit(
      absl::Overload(
          [&](GlobalRenderFrameHostId render_frame_host_id) {
            if (is_initiated_by_prefetch_) {
              // Falls back to the `CreateFromOriginAndIsolationInfo()` case
              // below.
              // Navigation isn't served by prefetch if the key for prefetch
              // calculated here is wrong/mismatching, checked at
              // `PrefetchURLLoaderInterceptor::OnGetPrefetchComplete()`.
              // https://crbug.com/413207408.
              return std::optional<blink::StorageKey>(std::nullopt);
            }
            // We use `ongoing_navigation_frame_tree_node_id_` instead of
            // `render_frame_host_id` because this method is called before
            // response commit.
            return GetStorageKeyFromRenderFrameHost(
                ongoing_navigation_frame_tree_node_id_, origin,
                base::OptionalToPtr(isolation_info_from_handle.nonce()));
          },
          [&](blink::DedicatedWorkerToken dedicated_worker_token) {
            auto* process = RenderProcessHost::FromID(GetProcessId());
            return process ? GetStorageKeyFromDedicatedWorkerHost(
                                 process->GetStoragePartition(),
                                 dedicated_worker_token, origin)
                           : std::nullopt;
          },
          [&](blink::SharedWorkerToken shared_worker_token) {
            auto* process = RenderProcessHost::FromID(GetProcessId());
            return process ? GetStorageKeyFromSharedWorkerHost(
                                 process->GetStoragePartition(),
                                 shared_worker_token, origin)
                           : std::nullopt;
          }),
      client_info_);

  if (storage_key) {
    return *storage_key;
  }

  // If we're in this case then we couldn't get the StorageKey from the RFH,
  // which means we also can't get the storage partitioning status from
  // RuntimeFeatureState(Read)Context. Using
  // CreateFromOriginAndIsolationInfo() will create a key based on
  // net::features::kThirdPartyStoragePartitioning state.
  return blink::StorageKey::CreateFromOriginAndIsolationInfo(
      origin, isolation_info_from_handle);
}

void ServiceWorkerClient::UpdateUrls(
    const GURL& creation_url,
    const std::optional<url::Origin>& top_frame_origin,
    const blink::StorageKey& storage_key) {
  CHECK(!is_response_committed());
  UpdateUrlsInternal(creation_url, top_frame_origin, storage_key);
}

void ServiceWorkerClient::UpdateUrlsAfterCommitResponseForTesting(
    const GURL& url,
    const std::optional<url::Origin>& top_frame_origin,
    const blink::StorageKey& storage_key) {
  CHECK(is_response_committed());
  UpdateUrlsInternal(url, top_frame_origin, storage_key);
}

void ServiceWorkerClient::SetControllerRegistration(
    scoped_refptr<ServiceWorkerRegistration> controller_registration,
    bool notify_controllerchange) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (controller_registration) {
    CHECK(IsEligibleForServiceWorkerController());
    CHECK(controller_registration->active_version());
    // TODO(https://crbug.com/526540644): CHECK-exclusion: Convert to CHECK once
    // we are sure this isn't hit.
    DCHECK(IsMatchingRegistration(controller_registration.get()));
  }

  controller_registration_ = controller_registration;
  UpdateController(notify_controllerchange);
}

bool ServiceWorkerClient::IsEligibleForServiceWorkerController() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  // A client hosted by a privileged WebContents that forbids service worker
  // control is permanently ineligible; this dominates every other check.
  if (disallows_service_worker_control_) {
    return false;
  }

  if (!url_.is_valid()) {
    return false;
  }
  // Pass GetUrlForScopeMatch() instead of `url_` because we cannot take the
  // origin of `url_` when it's a blob URL (see https://crbug.com/1144717). It's
  // guaranteed that the URL returned by GetURLForScopeMatch() has the same
  // logical origin as `url_`.
  // TODO(asamidoi): Add url::Origin member for ServiceWorkerContainerHost and
  // use it as the argument of OriginCanAccessServiceWorkers().
  if (!OriginCanAccessServiceWorkers(GetUrlForScopeMatch())) {
    return false;
  }

  if (is_parent_frame_secure_) {
    return true;
  }

  std::set<std::string> schemes;
  GetContentClient()->browser()->GetSchemesBypassingSecureContextCheckAllowlist(
      &schemes);
  return schemes.find(url_.GetScheme()) != schemes.end();
}

bool ServiceWorkerClient::is_response_committed() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (client_phase_) {
    case ClientPhase::kInitial:
    case ClientPhase::kResponseNotCommitted:
      return false;
    case ClientPhase::kResponseCommitted:
    case ClientPhase::kContainerReady:
    case ClientPhase::kExecutionReady:
      return true;
  }
}

bool ServiceWorkerClient::is_container_ready() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (client_phase_) {
    case ClientPhase::kInitial:
    case ClientPhase::kResponseCommitted:
    case ClientPhase::kResponseNotCommitted:
      return false;
    case ClientPhase::kContainerReady:
    case ClientPhase::kExecutionReady:
      return true;
  }
}

void ServiceWorkerClient::AddExecutionReadyCallback(
    ExecutionReadyCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!is_execution_ready());
  execution_ready_callbacks_.push_back(std::move(callback));
}

bool ServiceWorkerClient::is_execution_ready() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  switch (client_phase_) {
    case ClientPhase::kInitial:
    case ClientPhase::kResponseCommitted:
    case ClientPhase::kResponseNotCommitted:
    case ClientPhase::kContainerReady:
      return false;
    case ClientPhase::kExecutionReady:
      return true;
  }
}

void ServiceWorkerClient::SetDestructionCallbackForTesting(
    base::OnceClosure callback) {
  destruction_callback_for_testing_ = std::move(callback);
}

GlobalRenderFrameHostId ServiceWorkerClient::GetRenderFrameHostId() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsContainerForWindowClient());
  return std::get<GlobalRenderFrameHostId>(client_info_);
}

ChildProcessId ServiceWorkerClient::GetProcessId() const {
  if (IsContainerForWindowClient()) {
    return GetRenderFrameHostId().child_id;
  }
  DCHECK(IsContainerForWorkerClient());
  return process_id_for_worker_client_;
}

NavigationRequest* ServiceWorkerClient::GetOngoingNavigationRequestBeforeCommit(
    base::PassKey<StoragePartitionImpl>) const {
  DCHECK(IsContainerForWindowClient());
  DCHECK(!GetRenderFrameHostId());

  // For Window clients for prefetch,
  // `GetOngoingNavigationRequestBeforeCommit()` isn't called at all, because
  // prefetching requests don't set `URLLoaderNetworkServiceObserver`.
  CHECK(!is_initiated_by_prefetch_);

  // It is safe to use `ongoing_navigation_frame_tree_node_id_` to obtain the
  // corresponding navigation request without being concerned about the case
  // that a new navigation had started and the old navigation had been deleted,
  // because the owner of this instance will reset the key that can be used to
  // retrieve this instance, which makes the old key stale and cannot locate
  // this instance. This mechanism guarantees that this instance would always be
  // associated with the latest navigation.
  // However, this design requires callers to carefully get the
  // `ServiceWorkerContainerHost` instance from scratch instead of using a
  // stored one, and it would be better to optimize the design when possible.
  FrameTreeNode* frame_tree_node =
      FrameTreeNode::GloballyFindByID(ongoing_navigation_frame_tree_node_id_);
  return frame_tree_node ? frame_tree_node->navigation_request() : nullptr;
}

std::string ServiceWorkerClient::GetFrameTreeNodeTypeStringBeforeCommit()
    const {
  CHECK(!is_response_committed());
  // TODO(https://crbug.com/40947546): If needed, assign a proper metrics name
  // for clients for prefetch where `ongoing_navigation_frame_tree_node_id` is
  // null.
  if (FrameTreeNode* frame_tree_node = FrameTreeNode::GloballyFindByID(
          ongoing_navigation_frame_tree_node_id_)) {
    CHECK(IsContainerForWindowClient());
    return frame_tree_node->IsOutermostMainFrame() ? "OutermostMainFrame"
                                                   : "NotOutermostMainFrame";
  }
  return "Unknown";
}

const std::string& ServiceWorkerClient::client_uuid() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return client_uuid_;
}

std::string ServiceWorkerClient::client_uuid_for_resulting_client_id() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (is_initiated_by_prefetch_) {
    return "";
  }
  return client_uuid_;
}

ServiceWorkerVersion* ServiceWorkerClient::controller() const {
#if DCHECK_IS_ON()
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CheckControllerConsistency(false);
#endif  // DCHECK_IS_ON()
  return controller_.get();
}

ServiceWorkerRegistration* ServiceWorkerClient::controller_registration()
    const {
#if DCHECK_IS_ON()
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CheckControllerConsistency(false);
#endif  // DCHECK_IS_ON()
  return controller_registration_.get();
}

bool ServiceWorkerClient::IsInBackForwardCache() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  return is_in_back_forward_cache_;
}

void ServiceWorkerClient::EvictFromBackForwardCache(
    BackForwardCacheMetrics::NotRestoredReason reason) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsBackForwardCacheEnabled());
  is_in_back_forward_cache_ = false;

  if (!IsContainerForWindowClient()) {
    return;
  }

  auto* rfh = RenderFrameHostImpl::FromID(GetRenderFrameHostId());
  // |rfh| could be evicted before this function is called.
  if (rfh && rfh->IsInBackForwardCache()) {
    rfh->EvictFromBackForwardCacheWithReason(reason);
  }
}

void ServiceWorkerClient::OnEnterBackForwardCache() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsBackForwardCacheEnabled());
  if (controller_) {
    // TODO(crbug.com/330928087): remove check when this issue resolved.
    SCOPED_CRASH_KEY_NUMBER("SWC_OnEBFC", "client_type",
                            static_cast<int32_t>(GetClientType()));
    SCOPED_CRASH_KEY_BOOL("SWC_OnEBFC", "is_execution_ready",
                          is_execution_ready());
    SCOPED_CRASH_KEY_BOOL("SWC_OnEBFC", "is_blob_or_about_url",
                          url() != GetUrlForScopeMatch());
    SCOPED_CRASH_KEY_BOOL("SWC_OnEBFC", "is_inherited", is_inherited());
    CHECK(!controller_->BFCacheContainsControllee(client_uuid()));
    controller_->MoveControlleeToBackForwardCacheMap(client_uuid());
    was_controlled_when_entered_back_forward_cache_ = true;
  }
  is_in_back_forward_cache_ = true;
}

void ServiceWorkerClient::OnRestoreFromBackForwardCache() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(IsBackForwardCacheEnabled());
  // TODO(crbug.com/330928087): remove check when this issue resolved.
  SCOPED_CRASH_KEY_BOOL("SWC_OnRFBFC", "is_in_bfcache",
                        is_in_back_forward_cache_);
  SCOPED_CRASH_KEY_BOOL("SWC_OnRFBFC", "is_blob_or_about_url",
                        url() != GetUrlForScopeMatch());
  SCOPED_CRASH_KEY_BOOL("SWC_OnRFBFC", "is_inherited", is_inherited());
  if (controller_) {
    controller_->RestoreControlleeFromBackForwardCacheMap(client_uuid());
  }
  is_in_back_forward_cache_ = false;
  was_controlled_when_entered_back_forward_cache_ = false;
}

void ServiceWorkerClient::SyncMatchingRegistrations() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!controller_registration_);

  RemoveAllMatchingRegistrations();
  if (!context_) {
    return;
  }
  const auto& registrations = context_->GetLiveRegistrations();
  for (const auto& key_registration : registrations) {
    ServiceWorkerRegistration* registration = key_registration.second;
    if (!registration->is_uninstalled() && registration->key() == key() &&
        blink::ServiceWorkerScopeMatches(registration->scope(),
                                         GetUrlForScopeMatch())) {
      AddMatchingRegistration(registration);
    }
  }
}

bool ServiceWorkerClient::IsMatchingRegistration(
    ServiceWorkerRegistration* registration) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::string spec = registration->scope().spec();
  size_t key = spec.size();

  auto iter = matching_registrations_.find(key);
  if (iter == matching_registrations_.end()) {
    return false;
  }
  if (iter->second.get() != registration) {
    return false;
  }
  return true;
}

void ServiceWorkerClient::RemoveAllMatchingRegistrations() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(!controller_registration_);
  for (const auto& it : matching_registrations_) {
    ServiceWorkerRegistration* registration = it.second.get();
    registration->RemoveListener(this);
  }
  matching_registrations_.clear();
}

void ServiceWorkerClient::SetExecutionReady() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  switch (client_phase_) {
    case ClientPhase::kInitial:
      // When `CommitResponse()` is not yet called, it should be skipped because
      // the service worker client initialization failed somewhere, and thus
      // ignore the `SetExecutionReady()` call here and transition to
      // `kResponseNotCommitted` to confirm that no further transitions are
      // attempted. See also the comment at `kResponseNotCommitted` in the
      // header file.
      TransitionToClientPhase(ClientPhase::kResponseNotCommitted);
      break;

    case ClientPhase::kContainerReady:
      // Successful case.
      TransitionToClientPhase(ClientPhase::kExecutionReady);
      RunExecutionReadyCallbacks();

      if (context_) {
        context_->NotifyClientIsExecutionReady(*this);
      }

      FlushFeatures();
      break;

    case ClientPhase::kResponseCommitted:
    case ClientPhase::kExecutionReady:
    case ClientPhase::kResponseNotCommitted:
      // Invalid state transition.
      NOTREACHED()
          << "ServiceWorkerClient::SetExecutionReady() called on ClientPhase "
          << static_cast<int>(client_phase_);
  }
}

void ServiceWorkerClient::RunExecutionReadyCallbacks() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  std::vector<ExecutionReadyCallback> callbacks;
  execution_ready_callbacks_.swap(callbacks);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&RunCallbacks, std::move(callbacks)));
}

void ServiceWorkerClient::TransitionToClientPhase(ClientPhase new_phase) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (client_phase_ == new_phase) {
    return;
  }
  switch (client_phase_) {
    case ClientPhase::kInitial:
      CHECK(new_phase == ClientPhase::kResponseCommitted ||
            new_phase == ClientPhase::kResponseNotCommitted);
      break;
    case ClientPhase::kResponseCommitted:
      CHECK_EQ(new_phase, ClientPhase::kContainerReady);
      break;
    case ClientPhase::kContainerReady:
      CHECK_EQ(new_phase, ClientPhase::kExecutionReady);
      break;
    case ClientPhase::kExecutionReady:
    case ClientPhase::kResponseNotCommitted:
      NOTREACHED() << "Invalid transition from "
                   << static_cast<int>(client_phase_);
  }
  client_phase_ = new_phase;
}

void ServiceWorkerClient::UpdateController(bool notify_controllerchange) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ServiceWorkerVersion* version =
      controller_registration_ ? controller_registration_->active_version()
                               : nullptr;
  CHECK(!version || IsEligibleForServiceWorkerController());
  if (version == controller_.get()) {
    return;
  }

  scoped_refptr<ServiceWorkerVersion> previous_version = controller_;
  controller_ = version;
  was_controlled_when_entered_back_forward_cache_ = false;
  if (version) {
    // TODO(crbug.com/330928087): remove check when this issue resolved.
    SCOPED_CRASH_KEY_NUMBER("SWV_RCFBCM", "client_type",
                            static_cast<int32_t>(GetClientType()));
    SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_execution_ready",
                          is_execution_ready());
    SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_blob_or_about_url",
                          url() != GetUrlForScopeMatch());
    SCOPED_CRASH_KEY_BOOL("SWV_RCFBCM", "is_inherited", is_inherited());
    CHECK(!version->BFCacheContainsControllee(client_uuid()));
    version->AddControllee(this);
    if (IsBackForwardCacheEnabled() && IsInBackForwardCache()) {
      // |this| was not |version|'s controllee when |OnEnterBackForwardCache|
      // was called.
      version->MoveControlleeToBackForwardCacheMap(client_uuid());
      was_controlled_when_entered_back_forward_cache_ = true;
    }
    if (running_status_observer_) {
      version->AddObserver(running_status_observer_.get());
      running_status_observer_->Notify(version->running_status());
    }
  }
  if (previous_version) {
    previous_version->Uncontrol(client_uuid());
    if (running_status_observer_) {
      previous_version->RemoveObserver(running_status_observer_.get());
    }
  }

  // No need to `SetController` if the container is not ready because
  // when the container gets ready, `ControllerServiceWorkerInfoPtr` is also
  // sent in the same IPC call. Moreover, it is harmful to resend the past
  // SetController to the renderer because it moves the controller in the
  // renderer to the past one.
  if (!is_container_ready()) {
    return;
  }

  if (!is_execution_ready()) {
    return;
  }

  container_host()->SendSetController(notify_controllerchange);
}

#if DCHECK_IS_ON()
void ServiceWorkerClient::CheckControllerConsistency(bool should_crash) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!controller_) {
    DCHECK(!controller_registration_);
    return;
  }

  DCHECK(controller_registration_);
  DCHECK_EQ(controller_->registration_id(), controller_registration_->id());

  switch (controller_->status()) {
    case ServiceWorkerVersion::NEW:
    case ServiceWorkerVersion::INSTALLING:
    case ServiceWorkerVersion::INSTALLED:
      if (should_crash) {
        ServiceWorkerVersion::Status status = controller_->status();
        base::debug::Alias(&status);
        NOTREACHED() << "Controller service worker has a bad status: "
                     << ServiceWorkerVersion::VersionStatusToString(status);
      }
      break;
    case ServiceWorkerVersion::REDUNDANT: {
      if (should_crash) {
        NOTREACHED();
      }
      break;
    }
    case ServiceWorkerVersion::ACTIVATING:
    case ServiceWorkerVersion::ACTIVATED:
      // Valid status, controller is being activated.
      break;
  }
}
#endif  // DCHECK_IS_ON()

const GURL& ServiceWorkerClient::GetUrlForScopeMatch() const {
  if (!scope_match_url_for_client_.is_empty()) {
    return scope_match_url_for_client_;
  }
  return url_;
}

void ServiceWorkerClient::InheritControllerFrom(
    ServiceWorkerClient& creator_host,
    const GURL& client_url) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK(GetClientType() ==
             blink::mojom::ServiceWorkerClientType::kDedicatedWorker ||
         (base::FeatureList::IsEnabled(kSharedWorkerBlobURLFix) &&
          GetClientType() ==
              blink::mojom::ServiceWorkerClientType::kSharedWorker) ||
         (base::FeatureList::IsEnabled(features::kServiceWorkerSrcdocSupport) &&
          GetClientType() == blink::mojom::ServiceWorkerClientType::kWindow &&
          client_url.IsAboutSrcdoc()));
  // Only expect srcdoc url or blob url.
  DCHECK(client_url.SchemeIsBlob() ||
         (base::FeatureList::IsEnabled(features::kServiceWorkerSrcdocSupport) &&
          client_url.IsAboutSrcdoc()));
  // origins of blob client_url and creator host should be the same or both
  // opaque.
  if (client_url.SchemeIsBlob()) {
    service_worker_security_utils::CheckOnUpdateUrls(client_url,
                                                     creator_host.key());
  }

  // Inherit the creator's hard service-worker ineligibility. When set, the
  // creator is itself uncontrolled, so the controller-inheritance below is a
  // no-op (there is no controller to inherit), and the new client stays
  // permanently ineligible.
  if (creator_host.disallows_service_worker_control()) {
    SetDisallowsServiceWorkerControl();
  }

  // Let `scope_match_url_for_client_` be the creator's url for scope match
  // because a client should be handled by the service worker of its creator.
  // Update it before UpdateUrls so that CheckOnUpdateUrls inside UpdateUrls
  // checks with the updated GetUrlForScopeMatch().
  scope_match_url_for_client_ = creator_host.GetUrlForScopeMatch();

  UpdateUrls(client_url, creator_host.top_frame_origin(), creator_host.key());

  // Inherit the controller of the creator.
  if (creator_host.controller_registration()) {
    AddMatchingRegistration(creator_host.controller_registration());
    // If the creator is in back forward cache, the client should also
    // be in back forward cache. Otherwise, CHECK fail during restoring from
    // back forward cache.
    is_in_back_forward_cache_ = creator_host.is_in_back_forward_cache();
    // TODO(crbug.com/341322515): remove this CHECK.
    // This CHECK is to ensure this path does not cause the crash at
    // ServiceWorkerVersion::RemoveControlleeFromBackForwardCacheMap().
    CHECK(creator_host.controller_registration()->active_version());
    SetControllerRegistration(creator_host.controller_registration(),
                              false /* notify_controllerchange */);
  }
  creator_host.SetInherited();
}

mojo::PendingReceiver<blink::mojom::ServiceWorkerRunningStatusCallback>
ServiceWorkerClient::GetRunningStatusCallbackReceiver() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(controller_);
  if (!running_status_observer_) {
    running_status_observer_ =
        absl::make_unique<ServiceWorkerRunningStatusObserver>();
    controller_->AddObserver(running_status_observer_.get());
  }
  mojo::PendingRemote<blink::mojom::ServiceWorkerRunningStatusCallback>
      remote_callback;
  auto receiver = remote_callback.InitWithNewPipeAndPassReceiver();
  running_status_observer_->AddCallback(std::move(remote_callback));
  return receiver;
}

void ServiceWorkerClient::SetContainerReady() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  TransitionToClientPhase(ClientPhase::kContainerReady);
  CHECK(container_host()->IsContainerRemoteConnected());

  FlushFeatures();
}

void ServiceWorkerClient::FlushFeatures() {
  std::set<blink::mojom::WebFeature> features;
  features.swap(buffered_used_features_);
  for (const auto& feature : features) {
    CountFeature(feature);
  }
}

void ServiceWorkerClient::SetNetworkURLLoaderFactoryForTesting(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
  CHECK_IS_TEST();
  network_url_loader_factory_override_for_testing_ = url_loader_factory;
}

std::optional<ContentBrowserClient::URLLoaderRequestHandler>
ServiceWorkerClient::TakeInterceptingPreloadHandler(
    const network::ResourceRequest& resource_request) {
  CHECK(!is_response_committed());
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (is_initiated_by_prefetch_) {
    return std::nullopt;
  }

  std::optional<int64_t> navigation_id =
      GetOngoingNavigationId(ongoing_navigation_frame_tree_node_id_);
  CHECK(navigation_id);
  if (ContentBrowserClient::URLLoaderRequestHandler embedder_url_loader_handler =
          GetContentClient()
              ->browser()
              ->CreateURLLoaderHandlerForServiceWorkerInitiatedNavigationRequest(
                  ongoing_navigation_frame_tree_node_id_, resource_request,
                  *navigation_id,
                  GetUIThreadTaskRunner(
                      {BrowserTaskType::kNavigationNetworkResponse}))) {
    return std::move(embedder_url_loader_handler);
  }

  return std::nullopt;
}

scoped_refptr<network::SharedURLLoaderFactory>
ServiceWorkerClient::CreateNetworkURLLoaderFactory(
    CreateNetworkURLLoaderFactoryType type,
    StoragePartitionImpl* storage_partition,
    const network::ResourceRequest& resource_request,
    const base::UnguessableToken& network_restrictions_id) {
  CHECK(!is_response_committed());
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (network_url_loader_factory_override_for_testing_) {
    CHECK_IS_TEST();
    return network_url_loader_factory_override_for_testing_;
  }

  if (is_initiated_by_prefetch_) {
    // We skip `WillCreateURLLoaderFactory` below, because it is already
    // included in `network_url_loader_factory_for_prefetch_` (see
    // `PrefetchNetworkContext::CreateNewURLLoaderFactory()`).
    // We also skip
    // `CreateURLLoaderHandlerForServiceWorkerInitiatedNavigationRequest`,
    // because this is a prefetch request and don't have to consult with search
    // prefetch cache via
    // `CreateURLLoaderHandlerForServiceWorkerInitiatedNavigationRequest`.
    return network_url_loader_factory_for_prefetch_;
  }

  switch (type) {
    case CreateNetworkURLLoaderFactoryType::kNavigationPreload: {
      // Allow the embedder to intercept the URLLoader request if necessary.
      // This must be a synchronous decision by the embedder. In the future, we
      // may wish to support asynchronous decisions using
      // |URLLoaderRequestInterceptor| in the same fashion that they are used
      // for navigation requests.
      //
      // Note: Currently we skip passing `network_restrictions_id` here because
      // intercepted requests (such as Search Prefetch) are normally served
      // locally by the embedder or are only relevant to behavior triggered
      // outside the frame, like the omnibox. In the future, if an intercepted
      // request may trigger a new request on behalf of a specific context, we
      // need to ensure that the `network_restrictions_id` of that context is
      // provided.
      std::optional<int64_t> navigation_id =
          GetOngoingNavigationId(ongoing_navigation_frame_tree_node_id_);
      CHECK(navigation_id);
      if (ContentBrowserClient::URLLoaderRequestHandler embedder_url_loader_handler =
              GetContentClient()
                  ->browser()
                  ->CreateURLLoaderHandlerForServiceWorkerInitiatedNavigationRequest(
                      ongoing_navigation_frame_tree_node_id_, resource_request,
                      *navigation_id,
                      GetUIThreadTaskRunner(
                          {BrowserTaskType::kNavigationNetworkResponse}))) {
        return base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
            std::move(embedder_url_loader_handler));
      }
      break;
    }
    case CreateNetworkURLLoaderFactoryType::kRaceNetworkRequest:
    case CreateNetworkURLLoaderFactoryType::kSyntheticNetworkRequest:
      break;
  }

  // For worker clients, `ongoing_navigation_frame_tree_node_id_` is null.
  // TODO(falken): Can `navigation_request` check be a DCHECK now that the
  // caller does not post a task to this function?
  auto* frame_tree_node =
      FrameTreeNode::GloballyFindByID(ongoing_navigation_frame_tree_node_id_);
  if (!frame_tree_node || !storage_partition ||
      !frame_tree_node->navigation_request()) {
    // The navigation was cancelled. Just drop the request. Otherwise, we might
    // go to network without consulting the embedder first, which would break
    // guarantees.
    //
    // TODO(https://crbug.com/40947546): Clients for prefetch (where
    // `ongoing_navigation_frame_tree_node_id` is null) also fall into this case
    // and thus don't support navigationPreload and race network requests. Fix
    // this.
    mojo::PendingRemote<network::mojom::URLLoaderFactory> network_factory;
    return base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
        std::move(network_factory));
  }

  // Consult the embedder.
  mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
      header_client;
  network::URLLoaderFactoryBuilder factory_builder;
  // Here we give nullptr for |factory_override|, because CORS is no-op
  // for navigations.
  GetContentClient()->browser()->WillCreateURLLoaderFactory(
      storage_partition->browser_context(),
      frame_tree_node->current_frame_host(),
      frame_tree_node->current_frame_host()->GetProcess()->GetDeprecatedID(),
      ContentBrowserClient::URLLoaderFactoryType::kNavigation, url::Origin(),
      net::IsolationInfo(),
      frame_tree_node->navigation_request()->GetNavigationId(),
      ukm::SourceIdObj::FromInt64(
          frame_tree_node->navigation_request()->GetNextPageUkmSourceId()),
      factory_builder, &header_client, &bypass_redirect_checks_,
      /*disable_secure_dns=*/nullptr, /*factory_override=*/nullptr,
      GetUIThreadTaskRunner({BrowserTaskType::kNavigationNetworkResponse}),
      /*is_for_network_service=*/true);

  // Record the number of interceptors for metrics.
  factory_interceptor_count_ = factory_builder.num_interceptors();
  base::UmaHistogramCounts100(
      "ServiceWorker.URLLoaderFactoryInterceptorCountForMainResource",
      factory_builder.num_interceptors());

  // Make the network factory.
  return base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>(
      NavigationURLLoaderImpl::CreateURLLoaderFactoryWithHeaderClient(
          std::move(header_client), std::move(factory_builder),
          storage_partition,
          // TODO(crbug.com/390003764): Consider whether/how to apply devtools
          // cookies setting overrides for a service worker.
          /*devtools_cookie_overrides=*/std::nullopt,
          /*cookie_overrides=*/std::nullopt, network_restrictions_id));
}

// If a blob URL is used for a SharedWorker script's URL, a controller will be
// inherited.
BASE_FEATURE(kSharedWorkerBlobURLFix, base::FEATURE_ENABLED_BY_DEFAULT);

}  // namespace content
