[ServiceWorker+Prefetch] Implement serving side

Design doc:
https://docs.google.com/document/d/1kbs8YJuh93F_K6JqW84YSsjVWcAeU41Mj9nwPr6IXRs/edit?tab=t.0#heading=h.9fuqzdmgb6kv

After this CL, the primary cases for ServiceWorker-controlled prefetches
should work behind the flag (as shown in `basic.sub.https.html`), while
TODOs are still to be fixed.

Bug: 40947546
Change-Id: I86fc3f0ceaed0eda8aa67842cb23d29e796020eb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6021277
Commit-Queue: Hiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Ken Okada <kenoss@chromium.org>
Reviewed-by: Taiyo Mizuhashi <taiyo@chromium.org>
Reviewed-by: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Reviewed-by: Hiroki Nakagawa <nhiroki@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1433678}
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 6c34877..549abc0 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -39,6 +39,7 @@
 #include "content/browser/loader/subresource_proxying_url_loader_service.h"
 #include "content/browser/loader/url_loader_factory_utils.h"
 #include "content/browser/navigation_subresource_loader_params.h"
+#include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
@@ -670,6 +671,18 @@
     // The interceptor may not be created in certain cases (e.g., the origin
     // is not secure).
     if (service_worker_interceptor) {
+      if (base::FeatureList::IsEnabled(features::kPrefetchServiceWorker)) {
+        // Set up an interceptor for ServiceWorker-controlled prefetches. This
+        // is needed before the ServiceWorkerMainResourceLoaderInterceptor which
+        // would also intercept the request for ServiceWorker-controlled URLs.
+        // See the design docs at https://crbug.com/40947546.
+        interceptors_.push_back(std::make_unique<PrefetchURLLoaderInterceptor>(
+            PrefetchServiceWorkerState::kControlled,
+            service_worker_handle_->AsWeakPtr(), frame_tree_node_id_,
+            request_info_->initiator_document_token,
+            request_info_->prefetch_serving_page_metrics_container));
+      }
+
       interceptors_.push_back(std::move(service_worker_interceptor));
     }
   }
@@ -682,8 +695,14 @@
   }
 
   // Set up an interceptor for prefetch.
+  // When `features::kPrefetchServiceWorker` is enabled, we intentionally add
+  // two `PrefetchURLLoaderInterceptor`s, one for ServiceWorker-controlled
+  // prefetches above, and one for non-ServiceWorker-controlled prefetches here.
+  // See the design docs at https://crbug.com/40947546.
   interceptors_.push_back(std::make_unique<PrefetchURLLoaderInterceptor>(
-      frame_tree_node_id_, request_info_->initiator_document_token,
+      PrefetchServiceWorkerState::kDisallowed,
+      /*service_worker_handle=*/nullptr, frame_tree_node_id_,
+      request_info_->initiator_document_token,
       request_info_->prefetch_serving_page_metrics_container));
 
   // See if embedders want to add interceptors.
diff --git a/content/browser/preloading/prefetch/prefetch_match_resolver.cc b/content/browser/preloading/prefetch/prefetch_match_resolver.cc
index 3eb4354d9..2d67fbe 100644
--- a/content/browser/preloading/prefetch/prefetch_match_resolver.cc
+++ b/content/browser/preloading/prefetch/prefetch_match_resolver.cc
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/trace_event/trace_event.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
+#include "content/browser/preloading/prefetch/prefetch_features.h"
 #include "content/browser/preloading/prefetch/prefetch_params.h"
 #include "content/browser/preloading/prefetch/prefetch_service.h"
 #include "content/browser/preloading/prerender/prerender_features.h"
@@ -44,11 +45,23 @@
 
 PrefetchMatchResolver::PrefetchMatchResolver(
     PrefetchContainer::Key navigated_key,
+    PrefetchServiceWorkerState expected_service_worker_state,
     base::WeakPtr<PrefetchService> prefetch_service,
     Callback callback)
     : navigated_key_(std::move(navigated_key)),
+      expected_service_worker_state_(expected_service_worker_state),
       prefetch_service_(std::move(prefetch_service)),
-      callback_(std::move(callback)) {}
+      callback_(std::move(callback)) {
+  switch (expected_service_worker_state_) {
+    case PrefetchServiceWorkerState::kAllowed:
+      NOTREACHED();
+    case PrefetchServiceWorkerState::kControlled:
+      CHECK(base::FeatureList::IsEnabled(features::kPrefetchServiceWorker));
+      break;
+    case PrefetchServiceWorkerState::kDisallowed:
+      break;
+  }
+}
 
 PrefetchMatchResolver::~PrefetchMatchResolver() = default;
 
@@ -64,6 +77,7 @@
 // static
 void PrefetchMatchResolver::FindPrefetch(
     PrefetchContainer::Key navigated_key,
+    PrefetchServiceWorkerState expected_service_worker_state,
     bool is_nav_prerender,
     PrefetchService& prefetch_service,
     base::WeakPtr<PrefetchServingPageMetricsContainer>
@@ -72,8 +86,8 @@
   TRACE_EVENT0("loading", "PrefetchMatchResolver::FindPrefetch");
   // See the comment of `self_`.
   auto prefetch_match_resolver = base::WrapUnique(new PrefetchMatchResolver(
-      std::move(navigated_key), prefetch_service.GetWeakPtr(),
-      std::move(callback)));
+      std::move(navigated_key), expected_service_worker_state,
+      prefetch_service.GetWeakPtr(), std::move(callback)));
   PrefetchMatchResolver& ref = *prefetch_match_resolver.get();
   ref.self_ = std::move(prefetch_match_resolver);
 
@@ -91,7 +105,20 @@
       std::move(serving_page_metrics_container));
   // Consume `candidates`.
   for (auto& prefetch_container : candidates) {
-    RegisterCandidate(*prefetch_container);
+    // Register the candidate only if `PrefetchServiceWorkerState` is matching.
+    if (prefetch_container->service_worker_state() ==
+        expected_service_worker_state_) {
+      RegisterCandidate(*prefetch_container);
+    } else if (prefetch_container->service_worker_state() ==
+               PrefetchServiceWorkerState::kAllowed) {
+      // Also register the candidate if `PrefetchServiceWorkerState::kAllowed`,
+      // so that we anyway start BlockUntilHead (if eligible) before service
+      // worker controller check is done, and remove the candidate later (in
+      // `OnDeterminedHead()`) if the final `PrefetchServiceWorkerState` turns
+      // not matching.
+      CHECK(base::FeatureList::IsEnabled(features::kPrefetchServiceWorker));
+      RegisterCandidate(*prefetch_container);
+    }
   }
 
   // Backward compatibility to the behavior of `PrefetchMatchResolver`: If it
@@ -244,6 +271,25 @@
 
   // Note that `OnDeterimnedHead()` is called even if `PrefetchContainer` is in
   // failure `PrefetchState`. See, for example, https://crbug.com/375333786.
+
+  // Remove the candidate if the final `PrefetchServiceWorkerState` is not
+  // matching, i.e. we started speculatively BlockUntilHead on the candidate
+  // when `PrefetchServiceWorkerState::kAllowed`, but the final
+  // `PrefetchServiceWorkerState` found not matching here. At the time of
+  // `OnDeterminedHead()`, service worker controller check is always done and
+  // thus the final `PrefetchServiceWorkerState` is already determined here.
+  //
+  // TODO(https://crbug.com/40947546): Maybe we could unblock earlier when
+  // `PrefetchServiceWorkerState` is transitioned from `kAllowed`.
+  CHECK_NE(prefetch_container.service_worker_state(),
+           PrefetchServiceWorkerState::kAllowed);
+  if (prefetch_container.service_worker_state() !=
+      expected_service_worker_state_) {
+    CHECK(base::FeatureList::IsEnabled(features::kPrefetchServiceWorker));
+    MaybeUnblockForUnmatch(prefetch_container.key());
+    return;
+  }
+
   switch (prefetch_container.GetServableState(PrefetchCacheableDuration())) {
     case PrefetchContainer::ServableState::kShouldBlockUntilEligibilityGot:
       // All callsites of `PrefetchContainer::OnDeterminedHead()` are
@@ -309,6 +355,9 @@
   CHECK(candidate_data->prefetch_container);
   PrefetchContainer& prefetch_container = *candidate_data->prefetch_container;
 
+  CHECK_EQ(prefetch_container.service_worker_state(),
+           expected_service_worker_state_);
+
   UnregisterCandidate(prefetch_key, /*is_served=*/true);
 
   // Unregister remaining candidates as not served.
diff --git a/content/browser/preloading/prefetch/prefetch_match_resolver.h b/content/browser/preloading/prefetch/prefetch_match_resolver.h
index b04139b..4ca5a499 100644
--- a/content/browser/preloading/prefetch/prefetch_match_resolver.h
+++ b/content/browser/preloading/prefetch/prefetch_match_resolver.h
@@ -55,12 +55,17 @@
   //
   // This method is async. `callback` will be called when it is done.
   // `bool(reader)` is true iff a matching servable prefetch is found.
-  static void FindPrefetch(PrefetchContainer::Key navigated_key,
-                           bool is_nav_prerender,
-                           PrefetchService& prefetch_service,
-                           base::WeakPtr<PrefetchServingPageMetricsContainer>
-                               serving_page_metrics_container,
-                           Callback callback);
+  //
+  // Matches prefetches only if its final PrefetchServiceWorkerState is
+  // `expected_service_worker_state` (either `kControlled` or `kDisallowed`).
+  static void FindPrefetch(
+      PrefetchContainer::Key navigated_key,
+      PrefetchServiceWorkerState expected_service_worker_state,
+      bool is_nav_prerender,
+      PrefetchService& prefetch_service,
+      base::WeakPtr<PrefetchServingPageMetricsContainer>
+          serving_page_metrics_container,
+      Callback callback);
 
  private:
   struct CandidateData final {
@@ -71,9 +76,11 @@
     std::unique_ptr<base::OneShotTimer> timeout_timer;
   };
 
-  explicit PrefetchMatchResolver(PrefetchContainer::Key navigated_key,
-                                 base::WeakPtr<PrefetchService>,
-                                 Callback callback);
+  explicit PrefetchMatchResolver(
+      PrefetchContainer::Key navigated_key,
+      PrefetchServiceWorkerState expected_service_worker_state,
+      base::WeakPtr<PrefetchService>,
+      Callback callback);
 
   // Returns blocked duration. Returns null iff it's not blocked yet.
   std::optional<base::TimeDelta> GetBlockedDuration() const;
@@ -138,6 +145,7 @@
   std::unique_ptr<PrefetchMatchResolver> self_;
 
   const PrefetchContainer::Key navigated_key_;
+  const PrefetchServiceWorkerState expected_service_worker_state_;
   base::WeakPtr<PrefetchService> prefetch_service_;
   Callback callback_;
   std::map<PrefetchContainer::Key, std::unique_ptr<CandidateData>> candidates_;
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 816485c7..db1c5f2192 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -786,13 +786,11 @@
         NOTREACHED();
 
       case PrefetchServiceWorkerState::kControlled:
-        // Currently we disallow redirects for ServiceWorker-controlled
+        // Currently we disallow redirects from ServiceWorker-controlled
         // prefetches.
-        // TODO(https://crbug.com/40947546): Supply an appropriate eligibility
-        // value if needed.
         OnGotEligibility(std::move(prefetch_container),
                          std::move(redirect_data),
-                         PreloadingEligibility::kUserHasServiceWorker);
+                         PreloadingEligibility::kRedirectFromServiceWorker);
         return;
     }
   } else {
diff --git a/content/browser/preloading/prefetch/prefetch_service_unittest.cc b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
index e7297a4..c3e1156 100644
--- a/content/browser/preloading/prefetch/prefetch_service_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_service_unittest.cc
@@ -830,7 +830,8 @@
         BrowserContextImpl::From(browser_context())->GetPrefetchService();
       auto key = PrefetchContainer::Key(initiator_document_token, url);
       PrefetchMatchResolver::FindPrefetch(
-          std::move(key), /*is_nav_prerender=*/false, *prefetch_service,
+          std::move(key), PrefetchServiceWorkerState::kDisallowed,
+          /*is_nav_prerender=*/false, *prefetch_service,
           GetServingPageMetricsContainerForMostRecentNavigation(),
           std::move(callback));
   }
@@ -916,7 +917,8 @@
         BrowserContextImpl::From(browser_context())->GetPrefetchService();
     auto key = PrefetchContainer::Key(initiator_document_token, url);
     PrefetchMatchResolver::FindPrefetch(
-        std::move(key), is_nav_prerender, *prefetch_service,
+        std::move(key), PrefetchServiceWorkerState::kDisallowed,
+        is_nav_prerender, *prefetch_service,
         std::move(serving_page_metrics_container), std::move(callback));
 
     return res;
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
index 49e9b57..df6baa2 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.cc
@@ -21,6 +21,8 @@
 #include "content/browser/preloading/prerender/prerender_host_registry.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
+#include "content/browser/service_worker/service_worker_client.h"
+#include "content/browser/service_worker/service_worker_main_resource_handle.h"
 #include "content/public/browser/web_contents.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/single_request_url_loader_factory.h"
@@ -58,14 +60,25 @@
 }
 
 PrefetchURLLoaderInterceptor::PrefetchURLLoaderInterceptor(
+    PrefetchServiceWorkerState expected_service_worker_state,
+    base::WeakPtr<ServiceWorkerMainResourceHandle>
+        service_worker_handle_for_navigation,
     FrameTreeNodeId frame_tree_node_id,
     std::optional<blink::DocumentToken> initiator_document_token,
     base::WeakPtr<PrefetchServingPageMetricsContainer>
         serving_page_metrics_container)
-    : frame_tree_node_id_(frame_tree_node_id),
+    : expected_service_worker_state_(expected_service_worker_state),
+      service_worker_handle_for_navigation_(
+          std::move(service_worker_handle_for_navigation)),
+      frame_tree_node_id_(frame_tree_node_id),
       initiator_document_token_(std::move(initiator_document_token)),
       serving_page_metrics_container_(
-          std::move(serving_page_metrics_container)) {}
+          std::move(serving_page_metrics_container)) {
+  if (!base::FeatureList::IsEnabled(features::kPrefetchServiceWorker)) {
+    CHECK_EQ(expected_service_worker_state_,
+             PrefetchServiceWorkerState::kDisallowed);
+  }
+}
 
 PrefetchURLLoaderInterceptor::~PrefetchURLLoaderInterceptor() = default;
 
@@ -97,7 +110,8 @@
       OnGotPrefetchToServe(
           frame_tree_node_id_, tentative_resource_request.url,
           base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
-                         weak_factory_.GetWeakPtr()),
+                         weak_factory_.GetWeakPtr(),
+                         tentative_resource_request.url),
           std::move(redirect_reader_));
       return;
     }
@@ -123,10 +137,10 @@
     return;
   }
 
-  GetPrefetch(
-      tentative_resource_request,
-      base::BindOnce(&PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
-                     weak_factory_.GetWeakPtr()));
+  GetPrefetch(tentative_resource_request,
+              base::BindOnce(
+                  &PrefetchURLLoaderInterceptor::OnGetPrefetchComplete,
+                  weak_factory_.GetWeakPtr(), tentative_resource_request.url));
 }
 
 void PrefetchURLLoaderInterceptor::GetPrefetch(
@@ -171,11 +185,13 @@
     }();
 
     PrefetchMatchResolver::FindPrefetch(
-        std::move(key), is_nav_prerender, *prefetch_service,
-        serving_page_metrics_container_, std::move(callback));
+        std::move(key), expected_service_worker_state_, is_nav_prerender,
+        *prefetch_service, serving_page_metrics_container_,
+        std::move(callback));
 }
 
 void PrefetchURLLoaderInterceptor::OnGetPrefetchComplete(
+    GURL navigation_url,
     PrefetchContainer::Reader reader) {
   TRACE_EVENT0("loading",
                "PrefetchURLLoaderInterceptor::OnGetPrefetchComplete");
@@ -186,6 +202,21 @@
         reader.CreateRequestHandler();
   }
 
+  if (expected_service_worker_state_ ==
+          PrefetchServiceWorkerState::kControlled &&
+      request_handler) {
+    // ServiceWorker-controlled prefetch should be always non-redirecting.
+    CHECK(reader.IsEnd());
+
+    if (service_worker_handle_for_navigation_ && client_for_prefetch) {
+      service_worker_handle_for_navigation_->service_worker_client()
+          ->InheritControllerFromPrefetch(*client_for_prefetch, navigation_url);
+    } else {
+      // Do not intercept the request.
+      request_handler = PrefetchRequestHandler();
+    }
+  }
+
   if (!request_handler) {
     // Do not intercept the request.
     redirect_reader_ = PrefetchContainer::Reader();
@@ -211,6 +242,8 @@
     }
     redirect_reader_ = PrefetchContainer::Reader();
   } else {
+    CHECK_EQ(expected_service_worker_state_,
+             PrefetchServiceWorkerState::kDisallowed);
     redirect_reader_ = std::move(reader);
   }
 
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
index c8f8cab7..29cb3966 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor.h
@@ -21,6 +21,7 @@
 
 class BrowserContext;
 class PrefetchContainer;
+class ServiceWorkerMainResourceHandle;
 
 using PrefetchCompleteCallbackForTesting =
     base::RepeatingCallback<void(PrefetchContainer*)>;
@@ -30,6 +31,9 @@
     : public NavigationLoaderInterceptor {
  public:
   PrefetchURLLoaderInterceptor(
+      PrefetchServiceWorkerState expected_service_worker_state,
+      base::WeakPtr<ServiceWorkerMainResourceHandle>
+          service_worker_handle_for_navigation,
       FrameTreeNodeId frame_tree_node_id,
       std::optional<blink::DocumentToken> initiator_document_token,
       base::WeakPtr<PrefetchServingPageMetricsContainer>
@@ -65,7 +69,18 @@
                    base::OnceCallback<void(PrefetchContainer::Reader)>
                        get_prefetch_callback) const;
 
-  void OnGetPrefetchComplete(PrefetchContainer::Reader reader);
+  void OnGetPrefetchComplete(GURL navigation_url,
+                             PrefetchContainer::Reader reader);
+
+  // Matches prefetches only if its final PrefetchServiceWorkerState is
+  // `expected_service_worker_state_`, either `kControlled` or `kDisallowed`.
+  const PrefetchServiceWorkerState expected_service_worker_state_;
+
+  // `ServiceWorkerMainResourceHandle` used for the navigation to be intercepted
+  // (i.e. NOT the handle used for prefetch). This is used only for the
+  // `kControlled` case and can be null for `kDisallowed` case.
+  const base::WeakPtr<ServiceWorkerMainResourceHandle>
+      service_worker_handle_for_navigation_;
 
   // The frame tree node |this| is associated with, used to retrieve
   // |PrefetchService|.
diff --git a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
index 487ad75b..a8ee0fd 100644
--- a/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_url_loader_interceptor_unittest.cc
@@ -247,6 +247,8 @@
   void CreateInterceptor(
       std::optional<blink::DocumentToken> initiator_document_token) {
     interceptor_ = std::make_unique<PrefetchURLLoaderInterceptor>(
+        PrefetchServiceWorkerState::kDisallowed,
+        /*service_worker_handle=*/nullptr,
         web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId(),
         std::move(initiator_document_token),
         /*serving_page_metrics_container=*/nullptr);
diff --git a/content/browser/service_worker/service_worker_client.cc b/content/browser/service_worker/service_worker_client.cc
index 83fa5d7..b43d09a 100644
--- a/content/browser/service_worker/service_worker_client.cc
+++ b/content/browser/service_worker/service_worker_client.cc
@@ -17,6 +17,7 @@
 #include "base/uuid.h"
 #include "content/browser/child_process_security_policy_impl.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/service_worker/service_worker_container_host.h"
@@ -1158,6 +1159,28 @@
   creator_host.SetInherited();
 }
 
+void ServiceWorkerClient::InheritControllerFromPrefetch(
+    ServiceWorkerClient& client_for_prefetch,
+    const GURL& navigation_url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK(base::FeatureList::IsEnabled(features::kPrefetchServiceWorker));
+  CHECK(IsContainerForWindowClient());
+  CHECK(client_for_prefetch.IsContainerForWindowClient());
+
+  UpdateUrls(navigation_url, client_for_prefetch.top_frame_origin(),
+             client_for_prefetch.key());
+
+  // Inherit the controller used for prefetching from `client_for_prefetch`.
+  if (client_for_prefetch.controller_registration()) {
+    AddMatchingRegistration(client_for_prefetch.controller_registration());
+    // `client_for_prefetch` shouldn't be in back forward cache because it's for
+    // prefetch.
+    CHECK(!client_for_prefetch.is_in_back_forward_cache());
+    SetControllerRegistration(client_for_prefetch.controller_registration(),
+                              false /* notify_controllerchange */);
+  }
+}
+
 mojo::PendingReceiver<blink::mojom::ServiceWorkerRunningStatusCallback>
 ServiceWorkerClient::GetRunningStatusCallbackReceiver() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/content/browser/service_worker/service_worker_client.h b/content/browser/service_worker/service_worker_client.h
index c498a872..9f686512 100644
--- a/content/browser/service_worker/service_worker_client.h
+++ b/content/browser/service_worker/service_worker_client.h
@@ -355,6 +355,13 @@
   void InheritControllerFrom(ServiceWorkerClient& creator_host,
                              const GURL& client_url);
 
+  // For Window service worker clients served by ServiceWorker-controlled
+  // prefetch. Inherits the controller used for prefetching from
+  // `client_for_prefetch`, while setting `navigation_url`, similar to
+  // `InheritControllerFrom()`.
+  void InheritControllerFromPrefetch(ServiceWorkerClient& client_for_prefetch,
+                                     const GURL& navigation_url);
+
   void SetContainerReady();
 
   bool is_inherited() const { return is_inherited_; }
diff --git a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https.html b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https.html
index 9420088..e4c10353 100644
--- a/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https.html
+++ b/third_party/blink/web_tests/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https.html
@@ -25,10 +25,6 @@
 const swOption = new URL(location.href).searchParams.get('sw');
 
 promise_test(async t => {
-  // Current Chromium's expected behavior: prefetch only works when there
-  // are no controlling service worker.
-  const expectsPrefetch = swOption === 'no-controller';
-
   const hostname = originOption === 'cross-site' ? '{{hosts[alt][www]}}'
                                                  : undefined;
   const win = await spawnWindow(t, { protocol: 'https', hostname: hostname });
@@ -62,7 +58,12 @@
     return requestHeaders;
   }, []);
 
-  if (expectsPrefetch) {
+  // Current Chromium's expected behavior:
+  // prefetch works when
+  // - there are no controlling service worker, or
+  // - same-origin prefetches with controlling service worker.
+  if (swOption === 'no-controller' ||
+      originOption === 'same-site') {
     assert_prefetched(headers, "Prefetched result should be served.");
     assert_equals(requestCount.prefetch, 1,
         'a prefetch request should be sent to the server.');
diff --git a/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt b/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt
new file mode 100644
index 0000000..a093960
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Prefetch with ServiceWorker (fetch-handler)
+  assert_in_array: Prefetched result should be served. value "" not in array ["prefetch", "prefetch;anonymous-client-ip"]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt b/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt
new file mode 100644
index 0000000..41879b7
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Prefetch with ServiceWorker (fetch-handler-to-fallback)
+  assert_in_array: Prefetched result should be served. value "" not in array ["prefetch", "prefetch;anonymous-client-ip"]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt b/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt
new file mode 100644
index 0000000..1dd9caa
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Prefetch with ServiceWorker (no-fetch-handler)
+  assert_in_array: Prefetched result should be served. value "" not in array ["prefetch", "prefetch;anonymous-client-ip"]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt b/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt
deleted file mode 100644
index 53e39304..0000000
--- a/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Prefetch with ServiceWorker (fetch-handler)
-  assert_equals: prefetch requests should not be sent to the server. expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt b/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt
deleted file mode 100644
index b66b9a20..0000000
--- a/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Prefetch with ServiceWorker (fetch-handler-to-fallback)
-  assert_equals: prefetch requests should not be sent to the server. expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt b/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt
deleted file mode 100644
index 1bd90be..0000000
--- a/third_party/blink/web_tests/virtual/prefetch-serviceworker/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] Prefetch with ServiceWorker (no-fetch-handler)
-  assert_equals: prefetch requests should not be sent to the server. expected 0 but got 1
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt b/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt
new file mode 100644
index 0000000..a093960
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Prefetch with ServiceWorker (fetch-handler)
+  assert_in_array: Prefetched result should be served. value "" not in array ["prefetch", "prefetch;anonymous-client-ip"]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt b/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt
new file mode 100644
index 0000000..41879b7
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=fetch-handler-to-fallback-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Prefetch with ServiceWorker (fetch-handler-to-fallback)
+  assert_in_array: Prefetched result should be served. value "" not in array ["prefetch", "prefetch;anonymous-client-ip"]
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt b/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt
new file mode 100644
index 0000000..1dd9caa
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch/external/wpt/speculation-rules/prefetch/tentative/service-worker/basic.sub.https_origin=same-site_sw=no-fetch-handler-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] Prefetch with ServiceWorker (no-fetch-handler)
+  assert_in_array: Prefetched result should be served. value "" not in array ["prefetch", "prefetch;anonymous-client-ip"]
+Harness: the test ran to completion.
+