// Copyright 2019 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_loader_helpers.h"

#include <optional>
#include <string_view>

#include "base/byte_size.h"
#include "base/command_line.h"
#include "base/containers/lru_cache.h"
#include "base/no_destructor.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/loader/browser_initiated_resource_request.h"
#include "content/browser/renderer_preferences_util.h"
#include "content/browser/service_worker/service_worker_consts.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_metrics.h"
#include "content/browser/service_worker/service_worker_synthetic_response_manager.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/features.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/referrer.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
#include "third_party/blink/public/common/service_worker/service_worker_scope_match.h"
#include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"

namespace content::service_worker_loader_helpers {

namespace {

bool IsPathRestrictionSatisfiedInternal(
    const GURL& scope,
    const GURL& script_url,
    bool service_worker_allowed_header_supported,
    const std::optional<std::string_view>& service_worker_allowed_header_value,
    std::string* error_message) {
  DCHECK(scope.is_valid());
  DCHECK(!scope.has_ref());
  DCHECK(script_url.is_valid());
  DCHECK(!script_url.has_ref());
  DCHECK(error_message);

  if (blink::ServiceWorkerScopeOrScriptUrlContainsDisallowedCharacter(
          scope, script_url, error_message)) {
    return false;
  }

  std::string max_scope_string;
  if (service_worker_allowed_header_value &&
      service_worker_allowed_header_supported) {
    GURL max_scope = script_url.Resolve(*service_worker_allowed_header_value);
    if (!max_scope.is_valid()) {
      *error_message = "An invalid Service-Worker-Allowed header value ('";
      error_message->append(*service_worker_allowed_header_value);
      error_message->append("') was received when fetching the script.");
      return false;
    }

    if (max_scope.DeprecatedGetOriginAsURL() !=
        script_url.DeprecatedGetOriginAsURL()) {
      *error_message = "A cross-origin Service-Worker-Allowed header value ('";
      error_message->append(*service_worker_allowed_header_value);
      error_message->append("') was received when fetching the script.");
      return false;
    }
    max_scope_string = max_scope.GetPath();
  } else {
    max_scope_string = script_url.GetWithoutFilename().GetPath();
  }

  std::string scope_string = scope.GetPath();
  if (!base::StartsWith(scope_string, max_scope_string,
                        base::CompareCase::SENSITIVE)) {
    *error_message = "The path of the provided scope ('";
    error_message->append(scope_string);
    error_message->append("') is not under the max scope allowed (");
    if (service_worker_allowed_header_value &&
        service_worker_allowed_header_supported)
      error_message->append("set by Service-Worker-Allowed: ");
    error_message->append("'");
    error_message->append(max_scope_string);
    if (service_worker_allowed_header_supported) {
      error_message->append(
          "'). Adjust the scope, move the Service Worker script, or use the "
          "Service-Worker-Allowed HTTP header to allow the scope.");
    } else {
      error_message->append(
          "'). Adjust the scope or move the Service Worker script.");
    }
    return false;
  }
  return true;
}

bool HasUrlParamInSyntheticResponseDenyList(
    const GURL& url,
    const base::flat_set<std::string>& denied_url_params = {}) {
  if (denied_url_params.empty()) {
    return false;
  }
  for (net::QueryIterator it(url); !it.IsAtEnd(); it.Advance()) {
    if (denied_url_params.contains(it.GetKey())) {
      return true;
    }
  }
  return false;
}

bool IsUrlInSyntheticResponseAllowList(const GURL& client_url,
                                       const std::string& allowed_url) {
  if (allowed_url.empty()) {
    return false;
  }
  const GURL url(allowed_url);
  bool is_allowed = false;
  // TODO(crbug.com/352578800): It's OK to use `start_with()` as far as the
  // variation of given `client_url` value is limited, but consider
  // replacing it with the standard SW scope matching if possible.
  //
  // We intentionally ignore port matching as the port is dynamically decided
  // in tests, which is not predictable at the browser launch phase.
  if (client_url.scheme() == url.scheme() && client_url.host() == url.host() &&
      client_url.path() == url.path() &&
      client_url.query().starts_with(url.query())) {
    is_allowed = true;
  }

  if (!is_allowed) {
    return false;
  }

  return true;
}

const std::string& GetSyntheticResponseAllowedUrl() {
  static const base::NoDestructor<std::string> allowed_url(
      blink::features::kServiceWorkerSyntheticResponseAllowedUrl.Get());
  return *allowed_url;
}

const base::flat_set<std::string>& GetSyntheticResponseDeniedUrlParams() {
  static const base::NoDestructor<base::flat_set<std::string>>
      denied_url_params_set([]() {
        const std::string params_str(
            blink::features::kServiceWorkerSyntheticResponseDeniedUrlParams
                .Get());
        const std::vector<std::string_view> params = base::SplitStringPiece(
            params_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
        return base::flat_set<std::string>(params.begin(), params.end());
      }());
  return *denied_url_params_set;
}

}  // namespace

bool CheckResponseHead(
    const network::mojom::URLResponseHead& response_head,
    blink::ServiceWorkerStatusCode* out_service_worker_status,
    network::URLLoaderCompletionStatus* out_completion_status,
    std::string* out_error_message) {
  if (response_head.headers->response_code() / 100 != 2) {
    // Non-2XX HTTP status code is handled as an error.
    *out_completion_status =
        network::URLLoaderCompletionStatus(net::ERR_INVALID_RESPONSE);
    *out_error_message = base::StringPrintf(
        ServiceWorkerConsts::kServiceWorkerBadHTTPResponseError,
        response_head.headers->response_code());
    *out_service_worker_status = blink::ServiceWorkerStatusCode::kErrorNetwork;
    return false;
  }

  if (!devtools_instrumentation::ShouldBypassCertificateErrors() &&
      net::IsCertStatusError(response_head.cert_status) &&
      !base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kIgnoreCertificateErrors)) {
    *out_completion_status = network::URLLoaderCompletionStatus(
        net::MapCertStatusToNetError(response_head.cert_status));
    *out_error_message = ServiceWorkerConsts::kServiceWorkerSSLError;
    *out_service_worker_status = blink::ServiceWorkerStatusCode::kErrorNetwork;
    return false;
  }

  // Remain consistent with logic in
  // blink::InstalledServiceWorkerModuleScriptFetcher::Fetch()
  if (!blink::IsSupportedJavascriptMimeType(response_head.mime_type)) {
    *out_completion_status =
        network::URLLoaderCompletionStatus(net::ERR_INSECURE_RESPONSE);
    *out_error_message =
        response_head.mime_type.empty()
            ? ServiceWorkerConsts::kServiceWorkerNoMIMEError
            : base::StringPrintf(
                  ServiceWorkerConsts::kServiceWorkerBadMIMEError,
                  response_head.mime_type.c_str());
    *out_service_worker_status = blink::ServiceWorkerStatusCode::kErrorSecurity;
    return false;
  }

  return true;
}

bool ShouldBypassCacheDueToUpdateViaCache(
    bool is_main_script,
    blink::mojom::ServiceWorkerUpdateViaCache cache_mode) {
  switch (cache_mode) {
    case blink::mojom::ServiceWorkerUpdateViaCache::kImports:
      return is_main_script;
    case blink::mojom::ServiceWorkerUpdateViaCache::kNone:
      return true;
    case blink::mojom::ServiceWorkerUpdateViaCache::kAll:
      return false;
  }
  NOTREACHED() << static_cast<int>(cache_mode);
}

bool ShouldValidateBrowserCacheForScript(
    bool is_main_script,
    bool force_bypass_cache,
    blink::mojom::ServiceWorkerUpdateViaCache cache_mode,
    base::TimeDelta time_since_last_check) {
  return (ShouldBypassCacheDueToUpdateViaCache(is_main_script, cache_mode) ||
          time_since_last_check >
              ServiceWorkerConsts::kServiceWorkerScriptMaxCacheAge ||
          force_bypass_cache);
}

#if DCHECK_IS_ON()
void CheckVersionStatusBeforeWorkerScriptLoad(
    ServiceWorkerVersion::Status status,
    bool is_main_script,
    blink::mojom::ScriptType script_type) {
  if (is_main_script) {
    // The service worker main script should be fetched during worker startup.
    DCHECK_EQ(status, ServiceWorkerVersion::NEW);
    return;
  }

  // Non-main scripts are fetched by importScripts() for classic scripts or
  // static-import for module scripts.
  switch (script_type) {
    case blink::mojom::ScriptType::kClassic:
      // importScripts() should be called until completion of the install event.
      DCHECK(status == ServiceWorkerVersion::NEW ||
             status == ServiceWorkerVersion::INSTALLING);
      break;
    case blink::mojom::ScriptType::kModule:
      // Static-import should be handled during worker startup along with the
      // main script.
      DCHECK_EQ(status, ServiceWorkerVersion::NEW);
      break;
  }
}
#endif  // DCHECK_IS_ON()

network::ResourceRequest CreateRequestForServiceWorkerScript(
    const GURL& script_url,
    const blink::StorageKey& storage_key,
    bool is_main_script,
    blink::mojom::ScriptType worker_script_type,
    const blink::mojom::FetchClientSettingsObject& fetch_client_settings_object,
    BrowserContext& browser_context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  network::ResourceRequest request;
  request.url = script_url;

  // TODO(https://crbug.com/406525486): Permissions policies for workers are
  // currently not supported so an all-blocking permissions policy is set.
  // Propagate the actual permissions policy once it is available.
  request.permissions_policy =
      *network::PermissionsPolicy::CreateFromParsedPolicy(
          {}, url::Origin::Create(request.url));

  request.site_for_cookies = storage_key.ToNetSiteForCookies();
  request.do_not_prompt_for_login = true;

  blink::RendererPreferences renderer_preferences;
  UpdateRendererPreferencesForWorkerHelper(&browser_context,
                                           &renderer_preferences);
  UpdateAdditionalHeadersForBrowserInitiatedRequest(
      &request.headers, &browser_context,
      /*should_update_existing_headers=*/false, renderer_preferences,
      /*is_for_worker_script=*/true);

  // Set the accept header to '*/*'.
  // https://fetch.spec.whatwg.org/#concept-fetch
  request.headers.SetHeader(net::HttpRequestHeaders::kAccept,
                            network::kDefaultAcceptHeaderValue);

  request.referrer_policy = Referrer::ReferrerPolicyForUrlRequest(
      fetch_client_settings_object.policy_container_policies->referrer_policy);
  request.referrer =
      Referrer::SanitizeForRequest(
          script_url, Referrer(fetch_client_settings_object.outgoing_referrer,
                               fetch_client_settings_object
                                   .policy_container_policies->referrer_policy))
          .url;
  request.upgrade_if_insecure =
      fetch_client_settings_object.insecure_requests_policy ==
      blink::mojom::InsecureRequestsPolicy::kUpgrade;

  const url::Origin& origin = storage_key.origin();

  // ResourceRequest::request_initiator is the request's origin in the spec.
  // https://fetch.spec.whatwg.org/#concept-request-origin
  // It's needed to be set to the origin where the service worker is registered.
  // https://github.com/w3c/ServiceWorker/issues/1447
  request.request_initiator = origin;

  // This key is used to isolate requests from different contexts in accessing
  // shared network resources like the http cache.
  request.trusted_params = network::ResourceRequest::TrustedParams();
  request.trusted_params->isolation_info =
      storage_key.ToPartialNetIsolationInfo();

  if (worker_script_type == blink::mojom::ScriptType::kClassic) {
    if (is_main_script) {
      // Set the "Service-Worker" header for the service worker script request:
      // https://w3c.github.io/ServiceWorker/#service-worker-script-request
      request.headers.SetHeader("Service-Worker", "script");

      // The "Fetch a classic worker script" uses "same-origin" as mode and
      // credentials mode.
      // https://html.spec.whatwg.org/C/#fetch-a-classic-worker-script
      request.mode = network::mojom::RequestMode::kSameOrigin;
      request.credentials_mode = network::mojom::CredentialsMode::kSameOrigin;

      // The request's destination is "serviceworker" for the main script.
      // https://w3c.github.io/ServiceWorker/#update-algorithm
      request.destination = network::mojom::RequestDestination::kServiceWorker;
      request.resource_type =
          static_cast<int>(blink::mojom::ResourceType::kServiceWorker);
    } else {
      // The "fetch a classic worker-imported script" doesn't have any statement
      // about mode and credentials mode. Use the default value, which is
      // "no-cors".
      // https://html.spec.whatwg.org/C/#fetch-a-classic-worker-imported-script
      DCHECK_EQ(network::mojom::RequestMode::kNoCors, request.mode);

      // The request's destination is "script" for the imported script.
      // https://w3c.github.io/ServiceWorker/#update-algorithm
      request.destination = network::mojom::RequestDestination::kScript;
      request.resource_type =
          static_cast<int>(blink::mojom::ResourceType::kScript);
    }
  } else {
    if (is_main_script) {
      // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-worker-script-tree
      // Set the "Service-Worker" header for the service worker script request:
      // https://w3c.github.io/ServiceWorker/#service-worker-script-request
      request.headers.SetHeader("Service-Worker", "script");

      // The "Fetch a module worker script graph" uses "same-origin" as mode for
      // main script and "cors" otherwise.
      // https://w3c.github.io/ServiceWorker/#update-algorithm
      request.mode = network::mojom::RequestMode::kSameOrigin;
    } else {
      request.mode = network::mojom::RequestMode::kCors;
    }

    // The "Fetch a module worker script graph" uses "omit" as credentials
    // mode.
    // https://w3c.github.io/ServiceWorker/#update-algorithm
    request.credentials_mode = network::mojom::CredentialsMode::kOmit;

    // The request's destination is "serviceworker" for the main and
    // static-imported module script.
    // https://w3c.github.io/ServiceWorker/#update-algorithm
    request.destination = network::mojom::RequestDestination::kServiceWorker;
    request.resource_type =
        static_cast<int>(blink::mojom::ResourceType::kServiceWorker);
  }

  return request;
}

bool IsPathRestrictionSatisfied(
    const GURL& scope,
    const GURL& script_url,
    const std::optional<std::string_view>& service_worker_allowed_header_value,
    std::string* error_message) {
  return IsPathRestrictionSatisfiedInternal(scope, script_url, true,
                                            service_worker_allowed_header_value,
                                            error_message);
}

bool IsPathRestrictionSatisfiedWithoutHeader(const GURL& scope,
                                             const GURL& script_url,
                                             std::string* error_message) {
  return IsPathRestrictionSatisfiedInternal(scope, script_url, false,
                                            std::nullopt, error_message);
}

ServiceWorkerMainScriptRequestValidationResult ValidateMainScriptRequest(
    const network::ResourceRequest& resource_request,
    const ServiceWorkerVersion& version) {
  const bool is_url_match = (resource_request.url == version.script_url());
  const bool is_dest_match =
      (resource_request.destination ==
       network::mojom::RequestDestination::kServiceWorker);
  const bool is_mode_match =
      (resource_request.mode == network::mojom::RequestMode::kSameOrigin);

  if (is_dest_match) {
    if (!is_mode_match) {
      return ServiceWorkerMainScriptRequestValidationResult::kForgedMode;
    }
    if (!is_url_match) {
      return ServiceWorkerMainScriptRequestValidationResult::kForgedUrl;
    }
    return ServiceWorkerMainScriptRequestValidationResult::kOk;
  }

  if (is_mode_match && is_url_match) {
    return ServiceWorkerMainScriptRequestValidationResult::kForgedDestination;
  }

  // Not a main script request (or not forged in a way we track).
  return ServiceWorkerMainScriptRequestValidationResult::kOk;
}

const base::flat_set<std::string> FetchHandlerBypassedHashStrings() {
  const static base::NoDestructor<base::flat_set<std::string>> result(
      base::SplitString(
          features::kServiceWorkerBypassFetchHandlerBypassedHashStrings.Get(),
          ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY));

  return *result;
}

bool IsEligibleForSyntheticResponse(BrowserContext* browser_context,
                                    StoragePartitionImpl* storage_partition,
                                    const GURL& client_url) {
  if (!base::FeatureList::IsEnabled(
          blink::features::kServiceWorkerSyntheticResponse)) {
    return false;
  }
  return IsEligibleForSyntheticResponseInternal(
      browser_context, storage_partition, client_url,
      GetSyntheticResponseAllowedUrl(), GetSyntheticResponseDeniedUrlParams());
}

bool IsEligibleForSyntheticResponseForTesting(  // IN-TEST
    BrowserContext* browser_context,
    StoragePartitionImpl* storage_partition,
    const GURL& client_url,
    const std::string& allowed_url,
    const std::string& denied_url_params) {
  const std::vector<std::string_view> params = base::SplitStringPiece(
      denied_url_params, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  return IsEligibleForSyntheticResponseInternal(
      browser_context, storage_partition, client_url, allowed_url,
      base::flat_set<std::string>(params.begin(), params.end()));
}

bool IsEligibleForSyntheticResponseInternal(
    BrowserContext* browser_context,
    StoragePartitionImpl* storage_partition,
    const GURL& client_url,
    const std::string& allowed_url,
    const base::flat_set<std::string>& denied_url_params) {
  // If this StoragePartition is for guests (e.g., for a <webview>
  // tag). We don't enable it because the embedder may intercept the request.
  if (storage_partition && storage_partition->is_guest()) {
    return false;
  }
  // If `client_url` should be either 1) allowed by the browser content
  // client, or 2) listed in the allowlist.
  if ((browser_context &&
       GetContentClient()->browser()->IsServiceWorkerSyntheticResponseAllowed(
           browser_context, client_url)) ||
      IsUrlInSyntheticResponseAllowList(client_url, allowed_url)) {
    // And some URL params are not in the denylist.
    if (!HasUrlParamInSyntheticResponseDenyList(client_url,
                                                denied_url_params)) {
      return true;
    }
  }

  return false;
}

bool IsSyntheticResponseDryRunModeEnabled() {
  if (ServiceWorkerSyntheticResponseManager::IsDryRunModeEnabledForTesting()) {
    return true;
  }
  static const bool is_dry_run(
      blink::features::kServiceWorkerSyntheticResponseDryRun.Get());

  return is_dry_run;
}

storage::mojom::ServiceWorkerFindRegistrationResultPtr
GetOrCreateSyntheticRegistration(ServiceWorkerContextCore* context,
                                 const GURL& client_url,
                                 const blink::StorageKey& key) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Extract the first path segment (e.g. "/foo" from
  // "https://example.com/foo/bar") to establish a broader synthetic scope. This
  // guarantees that subsequent navigations across different paths within the
  // same search feature (e.g. "/foo/baz") successfully match the registration
  // scope without triggering longest-scope mismatch crashes.
  std::string_view path = client_url.path();
  size_t second_slash = path.find('/', 1);
  std::string first_path_segment(path.substr(0, second_slash));

  GURL::Replacements replacements_for_scope;
  replacements_for_scope.ClearQuery();
  replacements_for_scope.SetPathStr(first_path_segment);
  const auto kScope = client_url.ReplaceComponents(replacements_for_scope);

  // Cache registration IDs based on the StorageKey so that subsequent calls
  // for the same key return the same registration object. Using LRUCache to
  // prevent unbounded memory growth.
  constexpr int kMaxCachedRegistrations = 100;
  static base::NoDestructor<base::LRUCache<blink::StorageKey, int64_t>>
      key_to_registration_id_map(kMaxCachedRegistrations);

  static int64_t synthetic_id_counter = 0;
  int64_t registration_id;

  auto it = key_to_registration_id_map->Get(key);
  if (it != key_to_registration_id_map->end()) {
    registration_id = it->second;
    // If an in-memory registration already exists but its scope does not match
    // the requested synthetic scope (e.g. navigation across independent search
    // paths like /foo vs /baz), evict the cached ID and allocate a new one.
    // This prevents longest-scope mismatch crashes during navigation
    // validation.
    if (context) {
      if (scoped_refptr<ServiceWorkerRegistration> live_reg =
              context->GetLiveRegistration(registration_id);
          live_reg && live_reg->scope() != kScope) {
        key_to_registration_id_map->Erase(it);
        it = key_to_registration_id_map->end();
      }
    }
  }

  if (it == key_to_registration_id_map->end()) {
    registration_id =
        blink::mojom::kSyntheticResponseServiceWorkerRegistrationId -
        synthetic_id_counter;
    synthetic_id_counter++;
    key_to_registration_id_map->Put(key, registration_id);
  }

  GURL::Replacements replacements_for_script;
  replacements_for_script.ClearQuery();
  replacements_for_script.SetPathStr(
      "/service-worker-for-synthetic-response.js");
  const auto kScript = client_url.ReplaceComponents(replacements_for_script);

  std::vector<storage::mojom::ServiceWorkerResourceRecordPtr> resources;
  {
    auto resource = storage::mojom::ServiceWorkerResourceRecord::New(
        blink::mojom::kSyntheticResponseServiceWorkerResourceId, kScript,
        base::ByteSize(0),
        /*sha256_checksum=*/"");
    resources.push_back(std::move(resource));
  }

  auto data = storage::mojom::ServiceWorkerRegistrationData::New();
  data->registration_id = registration_id;
  data->scope = kScope;
  data->key = key;
  data->script = kScript;
  data->script_type = blink::mojom::ScriptType::kModule;
  data->update_via_cache = blink::mojom::ServiceWorkerUpdateViaCache::kNone;
  // Use `registration_id` as `version_id` to ensure each distinct StorageKey
  // gets its own unique ServiceWorkerVersion instance in memory, avoiding
  // cross-origin collisions in ServiceWorkerContextCore::live_versions_
  // (crbug.com/513205374).
  data->version_id = registration_id;
  data->is_active = true;
  data->fetch_handler_type =
      blink::mojom::ServiceWorkerFetchHandlerType::kNoHandler;
  data->last_update_check = base::Time::Now();
  data->navigation_preload_state = blink::mojom::NavigationPreloadState::New();

  {
    base::ByteSize resources_total_size;
    for (auto& resource : resources) {
      // `resource->size` can be unknown; sub in 0 here if so.
      // TODO(https://crbug.com/474382520): Add in error handling.
      resources_total_size += resource->size.value_or(base::ByteSize(0));
    }
    data->resources_total_size = resources_total_size;
  }

  data->script_response_time = base::Time::Now();
  data->ancestor_frame_type = blink::mojom::AncestorFrameType::kNormalFrame;
  data->policy_container_policies =
      blink::mojom::PolicyContainerPolicies::New();

  // Add the router rules to let all requests go to network source.
  {
    blink::ServiceWorkerRouterRules router_rules;
    {
      blink::ServiceWorkerRouterRule rule;
      {
        blink::ServiceWorkerRouterOrCondition or_condition;
        {
          blink::ServiceWorkerRouterRequestCondition request;
          request.mode = network::mojom::RequestMode::kNavigate;
          or_condition.conditions.push_back(
              blink::ServiceWorkerRouterCondition::WithRequest(
                  std::move(request)));
        }
        {
          blink::ServiceWorkerRouterNotCondition not_condition;
          {
            blink::ServiceWorkerRouterRequestCondition request;
            request.mode = network::mojom::RequestMode::kNavigate;
            not_condition.condition =
                std::make_unique<blink::ServiceWorkerRouterCondition>(
                    blink::ServiceWorkerRouterCondition::WithRequest(
                        std::move(request)));
          }
          or_condition.conditions.push_back(
              blink::ServiceWorkerRouterCondition::WithNotCondition(
                  std::move(not_condition)));
        }
        rule.condition = blink::ServiceWorkerRouterCondition::WithOrCondition(
            std::move(or_condition));
      }
      {
        blink::ServiceWorkerRouterSource source;
        source.type = network::mojom::ServiceWorkerRouterSourceType::kNetwork;
        source.network_source = blink::ServiceWorkerRouterNetworkSource{};
        rule.sources.emplace_back(std::move(source));
      }
      router_rules.rules.emplace_back(std::move(rule));
    }
    data->router_rules = std::move(router_rules);
  }

  mojo::PendingRemote<storage::mojom::ServiceWorkerLiveVersionRef>
      remote_reference;
  // We don't need to care about the receiver since this is a fake one.
  std::ignore = remote_reference.InitWithNewPipeAndPassReceiver();
  return storage::mojom::ServiceWorkerFindRegistrationResult::New(
      std::move(remote_reference), std::move(data), std::move(resources));
}

}  // namespace content::service_worker_loader_helpers
