Provide a high layer API to check SW's offline capability
This CL is the part in a series of CLs, roughly described as follows:
1. Refactor SeriviceWorkerTimeoutTimer
   Unify normal events and pending tasks.
2. Rename SeriviceWorkerTimeoutTimer to ServiceWorkerEventQueue.
3. Further clean up ServiceWorkerEventQueue.
4. Support offline events in ServiceWorkerEventQueue
5. Support offline mode in ServiceWorkerGlobalScope
6. Provide a high layer API to check SW's offline capability (<= this CL.)
This CL implements a high layer API,
|ServiceWorkerContext::CheckOfflineCapability(url, callback)|,
which is intended to be used from clients, such as Add2HomeScreen
feature, who want to know SW's offline capability.
A callback receives enum, |OfflineCapability|, whose meaning is:
- kSupported: SW exists for the url, and its fetch event handler can
  return a 200 result even in offline mode. We consider SW has an
  offline capability in this case.
- kUnsupported: Any other cases, such as:
  - SW does not exist.
  - SW's fetch handler can't return a 200 result in offline-mode.
  - internal errors such as disk failures, timeout, and etc.
Bug: 965802
Change-Id: If68cfeea2f3337b88a53a3cf0fd6ac87ee3bd66c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1900804
Commit-Queue: Hayato Ito <hayato@chromium.org>
Reviewed-by: Matt Falkenhagen <falken@chromium.org>
Reviewed-by: Makoto Shimazu <shimazu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#729678}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 0b91939b..c4bc9e5d 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1671,6 +1671,8 @@
     "service_worker/service_worker_new_script_loader.h",
     "service_worker/service_worker_object_host.cc",
     "service_worker/service_worker_object_host.h",
+    "service_worker/service_worker_offline_capability_checker.cc",
+    "service_worker/service_worker_offline_capability_checker.h",
     "service_worker/service_worker_ping_controller.cc",
     "service_worker/service_worker_ping_controller.h",
     "service_worker/service_worker_process_manager.cc",
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index c3f15d8..c185867 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -28,6 +28,7 @@
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_info.h"
 #include "content/browser/service_worker/service_worker_job_coordinator.h"
+#include "content/browser/service_worker/service_worker_offline_capability_checker.h"
 #include "content/browser/service_worker/service_worker_process_manager.h"
 #include "content/browser/service_worker/service_worker_register_job.h"
 #include "content/browser/service_worker/service_worker_registration.h"
@@ -730,6 +731,23 @@
                           AsWeakPtr(), std::move(callback)));
 }
 
+void ServiceWorkerContextCore::CheckOfflineCapability(
+    const GURL& url,
+    ServiceWorkerContext::CheckOfflineCapabilityCallback callback) {
+  auto checker = std::make_unique<ServiceWorkerOfflineCapabilityChecker>(url);
+  ServiceWorkerOfflineCapabilityChecker* checker_rawptr = checker.get();
+  checker_rawptr->Start(
+      storage(),
+      // Bind unique_ptr to the |callback| so that
+      // ServiceWorkerOfflineCapabilityChecker outlives |callback| and is surely
+      // freed when |callback| is called.
+      base::BindOnce(
+          [](std::unique_ptr<ServiceWorkerOfflineCapabilityChecker> checker,
+             ServiceWorkerContext::CheckOfflineCapabilityCallback callback,
+             OfflineCapability result) { std::move(callback).Run(result); },
+          std::move(checker), std::move(callback)));
+}
+
 void ServiceWorkerContextCore::UpdateVersionFailureCount(
     int64_t version_id,
     blink::ServiceWorkerStatusCode status) {
diff --git a/content/browser/service_worker/service_worker_context_core.h b/content/browser/service_worker/service_worker_context_core.h
index c62ed58..e4d57444 100644
--- a/content/browser/service_worker/service_worker_context_core.h
+++ b/content/browser/service_worker/service_worker_context_core.h
@@ -273,6 +273,13 @@
       const GURL& url,
       const ServiceWorkerContext::CheckHasServiceWorkerCallback callback);
 
+  // Returns OfflineCapability of the service worker matching |url|.
+  // See ServiceWorkerContext::CheckOfflineCapability for more
+  // details.
+  void CheckOfflineCapability(
+      const GURL& url,
+      const ServiceWorkerContext::CheckOfflineCapabilityCallback callback);
+
   void UpdateVersionFailureCount(int64_t version_id,
                                  blink::ServiceWorkerStatusCode status);
   // Returns the count of consecutive start worker failures for the given
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index 4dbda3c..aff5d08 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -638,6 +638,28 @@
                      this, std::move(callback)));
 }
 
+void ServiceWorkerContextWrapper::CheckOfflineCapability(
+    const GURL& url,
+    CheckOfflineCapabilityCallback callback) {
+  if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
+    base::PostTask(
+        FROM_HERE, {GetCoreThreadId()},
+        base::BindOnce(&ServiceWorkerContextWrapper::CheckOfflineCapability,
+                       this, url, std::move(callback)));
+    return;
+  }
+  if (!context_core_) {
+    base::PostTask(
+        FROM_HERE, {BrowserThread::UI},
+        base::BindOnce(std::move(callback), OfflineCapability::kUnsupported));
+    return;
+  }
+  context()->CheckOfflineCapability(
+      net::SimplifyUrlForRequest(url),
+      base::BindOnce(&ServiceWorkerContextWrapper::DidCheckOfflineCapability,
+                     this, std::move(callback)));
+}
+
 void ServiceWorkerContextWrapper::ClearAllServiceWorkersForTest(
     base::OnceClosure callback) {
   if (!BrowserThread::CurrentlyOn(GetCoreThreadId())) {
@@ -1651,6 +1673,14 @@
                  base::BindOnce(std::move(callback), capability));
 }
 
+void ServiceWorkerContextWrapper::DidCheckOfflineCapability(
+    CheckOfflineCapabilityCallback callback,
+    OfflineCapability capability) {
+  DCHECK_CURRENTLY_ON(GetCoreThreadId());
+  base::PostTask(FROM_HERE, {BrowserThread::UI},
+                 base::BindOnce(std::move(callback), capability));
+}
+
 void ServiceWorkerContextWrapper::DidFindRegistrationForUpdate(
     blink::ServiceWorkerStatusCode status,
     scoped_refptr<ServiceWorkerRegistration> registration) {
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index e07510a..e40bc6fe 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -159,6 +159,9 @@
   void PerformStorageCleanup(base::OnceClosure callback) override;
   void CheckHasServiceWorker(const GURL& url,
                              CheckHasServiceWorkerCallback callback) override;
+  void CheckOfflineCapability(const GURL& url,
+                              CheckOfflineCapabilityCallback callback) override;
+
   void ClearAllServiceWorkersForTest(base::OnceClosure callback) override;
   void StartWorkerForScope(const GURL& scope,
                            StartWorkerCallback info_callback,
@@ -388,6 +391,8 @@
 
   void DidCheckHasServiceWorker(CheckHasServiceWorkerCallback callback,
                                 content::ServiceWorkerCapability status);
+  void DidCheckOfflineCapability(CheckOfflineCapabilityCallback callback,
+                                 content::OfflineCapability status);
 
   void DidFindRegistrationForUpdate(
       blink::ServiceWorkerStatusCode status,
diff --git a/content/browser/service_worker/service_worker_offline_capability_check_browsertest.cc b/content/browser/service_worker/service_worker_offline_capability_check_browsertest.cc
index 0bf613c2..edce4d3 100644
--- a/content/browser/service_worker/service_worker_offline_capability_check_browsertest.cc
+++ b/content/browser/service_worker/service_worker_offline_capability_check_browsertest.cc
@@ -263,6 +263,32 @@
     test_helper.CheckResult();
   }
 
+  void CheckOfflineCapabilityOnCoreThread(
+      const std::string& path,
+      ServiceWorkerContext::CheckOfflineCapabilityCallback callback) {
+    wrapper()->CheckOfflineCapability(embedded_test_server()->GetURL(path),
+                                      std::move(callback));
+  }
+
+  OfflineCapability CheckOfflineCapability(const std::string& path) {
+    base::RunLoop fetch_run_loop;
+    base::Optional<OfflineCapability> out_offline_capability;
+    RunOrPostTaskOnThread(
+        FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
+        base::BindOnce(&ServiceWorkerOfflineCapabilityCheckBrowserTest::
+                           CheckOfflineCapabilityOnCoreThread,
+                       base::Unretained(this), path,
+                       base::BindLambdaForTesting(
+                           [&out_offline_capability, &fetch_run_loop](
+                               OfflineCapability offline_capability) {
+                             out_offline_capability = offline_capability;
+                             fetch_run_loop.Quit();
+                           })));
+    fetch_run_loop.Run();
+    DCHECK(out_offline_capability.has_value());
+    return *out_offline_capability;
+  }
+
  protected:
   // Expected results which are used in the tests.
   const FetchEventTestHelper::ExpectedResult kNetworkCompleted =
@@ -582,4 +608,80 @@
         kNetworkCompleted}});
 }
 
+// Sites without a service worker are identified as having no offline capability
+// support.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerOfflineCapabilityCheckBrowserTest,
+                       CheckOfflineCapabilityForNoServiceWorker) {
+  // We don't install ServiceWorker in this test.
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/empty.html"));
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/not_found.html"));
+}
+
+// Sites with a no-fetch-handler service worker are identified as having no
+// offline capability support.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerOfflineCapabilityCheckBrowserTest,
+                       CheckOfflineCapabilityForNoFetchHandler) {
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  // Install ServiceWorker which does not have any event handler.
+  EXPECT_EQ("DONE", EvalJs(shell(), "register('empty.js')"));
+
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/empty.html"));
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/not_found.html"));
+}
+
+// Sites with a service worker are identified as supporting offline capability
+// only when it returns a valid response in the offline mode.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerOfflineCapabilityCheckBrowserTest,
+                       CheckOfflineCapability) {
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  EXPECT_EQ("DONE", EvalJs(shell(), "register('maybe_offline_support.js')"));
+
+  // At this point, a service worker's status is ACTIVATED because
+  // register() awaits navigator.serviceWorker.ready promise.
+
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/out_of_scope.html"));
+
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/out_of_scope.html?offline"));
+
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/empty.html"));
+
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/empty.html?fetch"));
+
+  EXPECT_EQ(OfflineCapability::kSupported,
+            CheckOfflineCapability("/service_worker/empty.html?offline"));
+
+  EXPECT_EQ(
+      OfflineCapability::kSupported,
+      CheckOfflineCapability("/service_worker/empty.html?fetch_or_offline"));
+}
+
+// Sites with a service worker which is not activated yet are identified as
+// having no offline capability support.
+IN_PROC_BROWSER_TEST_F(ServiceWorkerOfflineCapabilityCheckBrowserTest,
+                       CheckOfflineCapabilityForInstallingServiceWorker) {
+  EXPECT_TRUE(NavigateToURL(shell(),
+                            embedded_test_server()->GetURL(
+                                "/service_worker/create_service_worker.html")));
+  // Appends |pendingInstallEvent| URL param to prevent a service worker from
+  // being activated.
+  EXPECT_EQ("DONE",
+            EvalJs(shell(),
+                   "registerWithoutAwaitingReady('maybe_offline_support.js?"
+                   "pendingInstallEvent')"));
+  EXPECT_EQ(OfflineCapability::kUnsupported,
+            CheckOfflineCapability("/service_worker/empty.html?offline"));
+}
+
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_offline_capability_checker.cc b/content/browser/service_worker/service_worker_offline_capability_checker.cc
new file mode 100644
index 0000000..958a1e8
--- /dev/null
+++ b/content/browser/service_worker/service_worker_offline_capability_checker.cc
@@ -0,0 +1,120 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// 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_offline_capability_checker.h"
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "content/browser/service_worker/service_worker_context_core.h"
+#include "content/browser/service_worker/service_worker_registration.h"
+#include "content/browser/service_worker/service_worker_version.h"
+#include "content/browser/storage_partition_impl.h"
+#include "url/gurl.h"
+
+namespace content {
+
+ServiceWorkerOfflineCapabilityChecker::ServiceWorkerOfflineCapabilityChecker(
+    const GURL& url)
+    : url_(url) {
+  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
+}
+
+ServiceWorkerOfflineCapabilityChecker::
+    ~ServiceWorkerOfflineCapabilityChecker() = default;
+
+void ServiceWorkerOfflineCapabilityChecker::Start(
+    ServiceWorkerStorage* storage,
+    ServiceWorkerContext::CheckOfflineCapabilityCallback callback) {
+  callback_ = std::move(callback);
+  storage->FindRegistrationForClientUrl(
+      url_, base::BindOnce(
+                &ServiceWorkerOfflineCapabilityChecker::DidFindRegistration,
+                // We can use base::Unretained(this) because |this| is expected
+                // to be alive until the |callback_| is called.
+                base::Unretained(this)));
+}
+
+void ServiceWorkerOfflineCapabilityChecker::DidFindRegistration(
+    blink::ServiceWorkerStatusCode status,
+    scoped_refptr<ServiceWorkerRegistration> registration) {
+  if (status != blink::ServiceWorkerStatusCode::kOk) {
+    std::move(callback_).Run(OfflineCapability::kUnsupported);
+    return;
+  }
+
+  if (registration->is_uninstalling() || registration->is_uninstalled()) {
+    std::move(callback_).Run(OfflineCapability::kUnsupported);
+    return;
+  }
+
+  scoped_refptr<ServiceWorkerVersion> preferred_version =
+      registration->active_version();
+  if (!preferred_version) {
+    preferred_version = registration->waiting_version();
+  }
+  if (!preferred_version) {
+    std::move(callback_).Run(OfflineCapability::kUnsupported);
+    return;
+  }
+
+  ServiceWorkerVersion::FetchHandlerExistence existence =
+      preferred_version->fetch_handler_existence();
+
+  DCHECK_NE(existence, ServiceWorkerVersion::FetchHandlerExistence::UNKNOWN);
+
+  if (existence != ServiceWorkerVersion::FetchHandlerExistence::EXISTS) {
+    std::move(callback_).Run(OfflineCapability::kUnsupported);
+    return;
+  }
+
+  if (preferred_version->status() != ServiceWorkerVersion::Status::ACTIVATING &&
+      preferred_version->status() != ServiceWorkerVersion::Status::ACTIVATED) {
+    // ServiceWorkerFetchDispatcher assumes that the version's status is
+    // ACTIVATING or ACTIVATED. If the version's status is other one, we return
+    // kUnsupported, without waiting its status becoming ACTIVATING because that
+    // is not always guaranteed.
+    // TODO(hayato): We can do a bit better, such as 1) trigger the activation
+    // and wait, or 2) return a value to indicate the service worker is
+    // installed but not yet activated.
+    std::move(callback_).Run(OfflineCapability::kUnsupported);
+    return;
+  }
+
+  auto request = blink::mojom::FetchAPIRequest::New();
+  request->url = url_;
+  request->method = "GET";
+  request->is_main_resource_load = true;
+
+  fetch_dispatcher_ = std::make_unique<ServiceWorkerFetchDispatcher>(
+      std::move(request), ResourceType::kMainFrame,
+      base::GenerateGUID() /* client_id */, std::move(preferred_version),
+      base::DoNothing() /* prepare callback */,
+      base::BindOnce(&ServiceWorkerOfflineCapabilityChecker::OnFetchResult,
+                     base::Unretained(this)),
+      /*is_offline_capability_check=*/true);
+  fetch_dispatcher_->Run();
+}
+
+void ServiceWorkerOfflineCapabilityChecker::OnFetchResult(
+    blink::ServiceWorkerStatusCode status,
+    ServiceWorkerFetchDispatcher::FetchEventResult result,
+    blink::mojom::FetchAPIResponsePtr response,
+    blink::mojom::ServiceWorkerStreamHandlePtr /* stream */,
+    blink::mojom::ServiceWorkerFetchEventTimingPtr /* timing */,
+    scoped_refptr<ServiceWorkerVersion>) {
+  if (status == blink::ServiceWorkerStatusCode::kOk &&
+      result == ServiceWorkerFetchDispatcher::FetchEventResult::kGotResponse &&
+      // TODO(hayato): Investigate whether any 2xx should be accepted or not.
+      response->status_code == 200) {
+    std::move(callback_).Run(OfflineCapability::kSupported);
+  } else {
+    // TODO(hayato): At present, we return kUnsupported even if the detection
+    // failed due to internal errors (disk fialures, timeout, etc). In the
+    // future, we might want to return another enum value so that the callside
+    // can know whether internal errors happened or not.
+    std::move(callback_).Run(OfflineCapability::kUnsupported);
+  }
+}
+
+}  // namespace content
diff --git a/content/browser/service_worker/service_worker_offline_capability_checker.h b/content/browser/service_worker/service_worker_offline_capability_checker.h
new file mode 100644
index 0000000..a3286ce
--- /dev/null
+++ b/content/browser/service_worker/service_worker_offline_capability_checker.h
@@ -0,0 +1,67 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_OFFLINE_CAPABILITY_CHECKER_H_
+#define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_OFFLINE_CAPABILITY_CHECKER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "content/browser/service_worker/service_worker_fetch_dispatcher.h"
+#include "content/public/browser/service_worker_context.h"
+#include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_fetch_response_callback.mojom.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_stream_handle.mojom.h"
+
+class GURL;
+
+namespace content {
+
+class ServiceWorkerRegistration;
+class ServiceWorkerStorage;
+class ServiceWorkerVersion;
+
+// Utility class used to check a service worker's offline capability.
+// Tracking bug is crbug.com/965802.
+//
+// |this| must outlive |callback_|.
+class ServiceWorkerOfflineCapabilityChecker {
+ public:
+  explicit ServiceWorkerOfflineCapabilityChecker(const GURL& url);
+  ~ServiceWorkerOfflineCapabilityChecker();
+
+  ServiceWorkerOfflineCapabilityChecker(
+      const ServiceWorkerOfflineCapabilityChecker&) = delete;
+  ServiceWorkerOfflineCapabilityChecker& operator=(
+      const ServiceWorkerOfflineCapabilityChecker&) = delete;
+
+  ServiceWorkerOfflineCapabilityChecker(
+      const ServiceWorkerOfflineCapabilityChecker&&) = delete;
+  ServiceWorkerOfflineCapabilityChecker& operator=(
+      const ServiceWorkerOfflineCapabilityChecker&&) = delete;
+
+  // It's the caller's responsibility to make sure that |this| outlives
+  // |callback|.
+  void Start(ServiceWorkerStorage* storage,
+             ServiceWorkerContext::CheckOfflineCapabilityCallback callback);
+
+ private:
+  void DidFindRegistration(
+      blink::ServiceWorkerStatusCode status,
+      scoped_refptr<ServiceWorkerRegistration> registration);
+
+  void OnFetchResult(
+      blink::ServiceWorkerStatusCode actual_status,
+      ServiceWorkerFetchDispatcher::FetchEventResult actual_result,
+      blink::mojom::FetchAPIResponsePtr actual_response,
+      blink::mojom::ServiceWorkerStreamHandlePtr /* stream */,
+      blink::mojom::ServiceWorkerFetchEventTimingPtr /* timing */,
+      scoped_refptr<ServiceWorkerVersion> worker);
+
+  const GURL url_;
+  ServiceWorkerContext::CheckOfflineCapabilityCallback callback_;
+  std::unique_ptr<ServiceWorkerFetchDispatcher> fetch_dispatcher_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_OFFLINE_CAPABILITY_CHECKER_H_
diff --git a/content/public/browser/service_worker_context.h b/content/public/browser/service_worker_context.h
index 60035aa8..1544a2f 100644
--- a/content/public/browser/service_worker_context.h
+++ b/content/public/browser/service_worker_context.h
@@ -35,6 +35,11 @@
   SERVICE_WORKER_WITH_FETCH_HANDLER,
 };
 
+enum class OfflineCapability {
+  kUnsupported,
+  kSupported,
+};
+
 // Used for UMA. Append only.
 enum class StartServiceWorkerForNavigationHintResult {
   // The service worker started successfully.
@@ -68,6 +73,9 @@
   using CheckHasServiceWorkerCallback =
       base::OnceCallback<void(ServiceWorkerCapability capability)>;
 
+  using CheckOfflineCapabilityCallback =
+      base::OnceCallback<void(OfflineCapability capability)>;
+
   using CountExternalRequestsCallback =
       base::OnceCallback<void(size_t external_request_count)>;
 
@@ -175,6 +183,19 @@
       const GURL& url,
       CheckHasServiceWorkerCallback callback) = 0;
 
+  // Simulates a navigation request in the offline state and dispatches a fetch
+  // event. Returns OfflineCapability::kSupported if the response's status code
+  // is 200.
+  //
+  // This function can be called from any thread, but the callback will always
+  // be called on the UI thread.
+  //
+  // TODO(hayato): Re-visit to integrate this function with
+  // |ServiceWorkerContext::CheckHasServiceWorker|.
+  virtual void CheckOfflineCapability(
+      const GURL& url,
+      CheckOfflineCapabilityCallback callback) = 0;
+
   // Stops all running service workers and unregisters all service worker
   // registrations. This method is used in web tests to make sure that the
   // existing service worker will not affect the succeeding tests.
diff --git a/content/public/test/fake_service_worker_context.cc b/content/public/test/fake_service_worker_context.cc
index ab62c2be3..4a21d33 100644
--- a/content/public/test/fake_service_worker_context.cc
+++ b/content/public/test/fake_service_worker_context.cc
@@ -72,6 +72,11 @@
     CheckHasServiceWorkerCallback callback) {
   NOTREACHED();
 }
+void FakeServiceWorkerContext::CheckOfflineCapability(
+    const GURL& url,
+    const ServiceWorkerContext::CheckOfflineCapabilityCallback callback) {
+  NOTREACHED();
+}
 void FakeServiceWorkerContext::ClearAllServiceWorkersForTest(
     base::OnceClosure) {
   NOTREACHED();
diff --git a/content/public/test/fake_service_worker_context.h b/content/public/test/fake_service_worker_context.h
index 9fdfdf72..5af8077 100644
--- a/content/public/test/fake_service_worker_context.h
+++ b/content/public/test/fake_service_worker_context.h
@@ -53,6 +53,10 @@
   void PerformStorageCleanup(base::OnceClosure callback) override;
   void CheckHasServiceWorker(const GURL& url,
                              CheckHasServiceWorkerCallback callback) override;
+  void CheckOfflineCapability(
+      const GURL& url,
+      const ServiceWorkerContext::CheckOfflineCapabilityCallback callback)
+      override;
   void ClearAllServiceWorkersForTest(base::OnceClosure) override;
   void StartWorkerForScope(
       const GURL& scope,
diff --git a/content/test/data/service_worker/create_service_worker.html b/content/test/data/service_worker/create_service_worker.html
index 2d47c2a..10f550a7 100644
--- a/content/test/data/service_worker/create_service_worker.html
+++ b/content/test/data/service_worker/create_service_worker.html
@@ -12,6 +12,17 @@
   }
 }
 
+async function registerWithoutAwaitingReady(worker_url, scope) {
+  try {
+    const init = scope ? {scope} : {};
+    await navigator.serviceWorker.register(worker_url, init);
+    // Don't await for navigator.serviceWorker.ready.
+    return 'DONE';
+  } catch (error) {
+    return `${error}`;
+  }
+}
+
 async function update(scope) {
   try {
     const registration =
diff --git a/content/test/data/service_worker/maybe_offline_support.js b/content/test/data/service_worker/maybe_offline_support.js
index ec825ee1..b04d302 100644
--- a/content/test/data/service_worker/maybe_offline_support.js
+++ b/content/test/data/service_worker/maybe_offline_support.js
@@ -10,6 +10,14 @@
   });
 }
 
+const scriptUrlParams = new URL(self.serviceWorker.scriptURL).searchParams;
+if (scriptUrlParams.has('pendingInstallEvent')) {
+  // Prevents this SW from being activated.
+  self.addEventListener('install', e => {
+    e.waitUntil(new Promise(resolve => {}));
+  });
+}
+
 self.addEventListener("fetch", event => {
   const param = new URL(event.request.url).searchParams;