blob: f89c36ba83663a88621fe3a7f0dd32146a59f0af [file] [log] [blame]
// Copyright 2015 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_client_utils.h"
#include <algorithm>
#include <memory>
#include <tuple>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_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_context_wrapper.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/payment_app_provider.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_client.h"
#include "content/public/common/page_visibility_state.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/loader/request_context_frame_type.mojom.h"
#include "ui/base/window_open_disposition.h"
#include "url/gurl.h"
#include "url/origin.h"
// TODO(https://crbug.com/824858): Much of this file, which dealt with thread
// hops between UI and IO, can likely be simplified now that service worker code
// is on the UI thread.
namespace content {
namespace service_worker_client_utils {
namespace {
using OpenURLCallback = base::OnceCallback<void(GlobalRenderFrameHostId)>;
// The OpenURLObserver class is a WebContentsObserver that will wait for a
// WebContents to be initialized, run the |callback| passed to its constructor
// then self destroy.
// The callback will receive the GlobalRenderFrameHostId. If something went
// wrong it will have MSG_ROUTING_NONE.
class OpenURLObserver : public WebContentsObserver {
public:
OpenURLObserver(WebContents* web_contents,
int frame_tree_node_id,
OpenURLCallback callback)
: WebContentsObserver(web_contents),
frame_tree_node_id_(frame_tree_node_id),
callback_(std::move(callback)) {}
void DidFinishNavigation(NavigationHandle* navigation_handle) override {
if (navigation_handle->GetFrameTreeNodeId() != frame_tree_node_id_) {
// This navigation is not for the frame this observer is interested in,
// return and keeping observing.
return;
}
if (!navigation_handle->HasCommitted()) {
// Return error.
RunCallback(GlobalRenderFrameHostId());
return;
}
RenderFrameHost* render_frame_host =
navigation_handle->GetRenderFrameHost();
RunCallback(render_frame_host->GetGlobalId());
}
void RenderProcessGone(base::TerminationStatus status) override {
RunCallback(GlobalRenderFrameHostId());
}
void WebContentsDestroyed() override {
RunCallback(GlobalRenderFrameHostId());
}
private:
void RunCallback(const GlobalRenderFrameHostId& rfh_id) {
// After running the callback, |this| will stop observing, thus
// web_contents() should return nullptr and |RunCallback| should no longer
// be called. Then, |this| will self destroy.
DCHECK(web_contents());
DCHECK(callback_);
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::SequencedTaskRunnerHandle::Get();
// TODO(falken): Does this need to be asynchronous?
task_runner->PostTask(FROM_HERE,
base::BindOnce(std::move(callback_), rfh_id));
Observe(nullptr);
task_runner->DeleteSoon(FROM_HERE, this);
}
int frame_tree_node_id_;
OpenURLCallback callback_;
DISALLOW_COPY_AND_ASSIGN(OpenURLObserver);
};
blink::mojom::ServiceWorkerClientInfoPtr GetWindowClientInfo(
const GlobalRenderFrameHostId& rfh_id,
base::TimeTicks create_time,
const std::string& client_uuid) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* render_frame_host = RenderFrameHostImpl::FromID(rfh_id);
if (!render_frame_host)
return nullptr;
// Treat items in backforward cache as not existing.
if (render_frame_host->IsInBackForwardCache())
return nullptr;
// TODO(mlamouri,michaeln): it is possible to end up collecting information
// for a frame that is actually being navigated and isn't exactly what we are
// expecting.
PageVisibilityState visibility = render_frame_host->GetVisibilityState();
bool page_hidden = visibility != PageVisibilityState::kVisible;
return blink::mojom::ServiceWorkerClientInfo::New(
render_frame_host->GetLastCommittedURL(),
render_frame_host->GetParent()
? blink::mojom::RequestContextFrameType::kNested
: blink::mojom::RequestContextFrameType::kTopLevel,
client_uuid, blink::mojom::ServiceWorkerClientType::kWindow, page_hidden,
render_frame_host->IsFocused(),
render_frame_host->IsFrozen()
? blink::mojom::ServiceWorkerClientLifecycleState::kFrozen
: blink::mojom::ServiceWorkerClientLifecycleState::kActive,
render_frame_host->frame_tree_node()->last_focus_time(), create_time);
}
// This is only called for main frame navigations in OpenWindow().
void DidOpenURL(OpenURLCallback callback, WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents) {
std::move(callback).Run(GlobalRenderFrameHostId());
return;
}
// ContentBrowserClient::OpenURL calls ui::BaseWindow::Show which
// makes the destination window the main+key window, but won't make Chrome
// the active application (https://crbug.com/470830). Since OpenWindow is
// always called from a user gesture (e.g. notification click), we should
// explicitly activate the window, which brings Chrome to the front.
static_cast<WebContentsImpl*>(web_contents)->Activate();
RenderFrameHostImpl* rfhi =
static_cast<RenderFrameHostImpl*>(web_contents->GetMainFrame());
new OpenURLObserver(web_contents,
rfhi->frame_tree_node()->frame_tree_node_id(),
std::move(callback));
}
void AddWindowClient(
const ServiceWorkerContainerHost* container_host,
std::vector<
std::tuple<GlobalRenderFrameHostId, base::TimeTicks, std::string>>*
client_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!container_host->IsContainerForWindowClient()) {
return;
}
if (!container_host->is_execution_ready())
return;
client_info->push_back(std::make_tuple(container_host->GetRenderFrameHostId(),
container_host->create_time(),
container_host->client_uuid()));
}
void AddNonWindowClient(
const ServiceWorkerContainerHost* container_host,
blink::mojom::ServiceWorkerClientType client_type,
std::vector<blink::mojom::ServiceWorkerClientInfoPtr>* out_clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::mojom::ServiceWorkerClientType host_client_type =
container_host->GetClientType();
if (host_client_type == blink::mojom::ServiceWorkerClientType::kWindow)
return;
if (client_type != blink::mojom::ServiceWorkerClientType::kAll &&
client_type != host_client_type)
return;
if (!container_host->is_execution_ready())
return;
// TODO(dtapuska): Need to get frozen state for dedicated workers from
// DedicatedWorkerHost. crbug.com/968417
auto client_info = blink::mojom::ServiceWorkerClientInfo::New(
container_host->url(), blink::mojom::RequestContextFrameType::kNone,
container_host->client_uuid(), host_client_type,
/*page_hidden=*/true,
/*is_focused=*/false,
blink::mojom::ServiceWorkerClientLifecycleState::kActive,
base::TimeTicks(), container_host->create_time());
out_clients->push_back(std::move(client_info));
}
struct ServiceWorkerClientInfoSort {
bool operator()(const blink::mojom::ServiceWorkerClientInfoPtr& a,
const blink::mojom::ServiceWorkerClientInfoPtr& b) const {
// Clients for windows should be appeared earlier.
if (a->client_type == blink::mojom::ServiceWorkerClientType::kWindow &&
b->client_type != blink::mojom::ServiceWorkerClientType::kWindow) {
return true;
}
if (a->client_type != blink::mojom::ServiceWorkerClientType::kWindow &&
b->client_type == blink::mojom::ServiceWorkerClientType::kWindow) {
return false;
}
// Clients focused recently should be appeared earlier.
if (a->last_focus_time != b->last_focus_time)
return a->last_focus_time > b->last_focus_time;
// Clients created before should be appeared earlier.
return a->creation_time < b->creation_time;
}
};
void DidGetClients(
blink::mojom::ServiceWorkerHost::GetClientsCallback callback,
std::vector<blink::mojom::ServiceWorkerClientInfoPtr> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::sort(clients.begin(), clients.end(), ServiceWorkerClientInfoSort());
std::move(callback).Run(std::move(clients));
}
void GetNonWindowClients(
const base::WeakPtr<ServiceWorkerVersion>& controller,
blink::mojom::ServiceWorkerClientQueryOptionsPtr options,
blink::mojom::ServiceWorkerHost::GetClientsCallback callback,
std::vector<blink::mojom::ServiceWorkerClientInfoPtr> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (options->include_uncontrolled) {
if (controller->context()) {
for (auto it = controller->context()->GetClientContainerHostIterator(
controller->key(), false /* include_reserved_clients */,
false /* include_back_forward_cached_clients */);
!it->IsAtEnd(); it->Advance()) {
AddNonWindowClient(it->GetContainerHost(), options->client_type,
&clients);
}
}
} else {
for (const auto& controllee : controller->controllee_map())
AddNonWindowClient(controllee.second, options->client_type, &clients);
}
DidGetClients(std::move(callback), std::move(clients));
}
void DidGetWindowClients(
const base::WeakPtr<ServiceWorkerVersion>& controller,
blink::mojom::ServiceWorkerClientQueryOptionsPtr options,
blink::mojom::ServiceWorkerHost::GetClientsCallback callback,
std::vector<blink::mojom::ServiceWorkerClientInfoPtr> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (options->client_type == blink::mojom::ServiceWorkerClientType::kAll) {
GetNonWindowClients(controller, std::move(options), std::move(callback),
std::move(clients));
return;
}
DidGetClients(std::move(callback), std::move(clients));
}
void GetWindowClients(
const base::WeakPtr<ServiceWorkerVersion>& controller,
blink::mojom::ServiceWorkerClientQueryOptionsPtr options,
blink::mojom::ServiceWorkerHost::GetClientsCallback callback,
std::vector<blink::mojom::ServiceWorkerClientInfoPtr> clients) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(options->client_type ==
blink::mojom::ServiceWorkerClientType::kWindow ||
options->client_type == blink::mojom::ServiceWorkerClientType::kAll);
// TODO(falken): Clean this up. We shouldn't need an intermediate
// `clients_info` and can just add to `clients` directly.
std::vector<std::tuple<GlobalRenderFrameHostId, base::TimeTicks, std::string>>
clients_info;
if (options->include_uncontrolled) {
if (controller->context()) {
for (auto it = controller->context()->GetClientContainerHostIterator(
controller->key(), false /* include_reserved_clients */,
false /* include_back_forward_cached_clients */);
!it->IsAtEnd(); it->Advance()) {
AddWindowClient(it->GetContainerHost(), &clients_info);
}
}
} else {
for (const auto& controllee : controller->controllee_map())
AddWindowClient(controllee.second, &clients_info);
}
if (clients_info.empty()) {
DidGetWindowClients(controller, std::move(options), std::move(callback),
std::move(clients));
return;
}
for (const auto& it : clients_info) {
blink::mojom::ServiceWorkerClientInfoPtr info =
GetWindowClientInfo(std::get<0>(it), std::get<1>(it), std::get<2>(it));
// If the request to the container_host returned a null
// ServiceWorkerClientInfo, that means that it wasn't possible to associate
// it with a valid RenderFrameHost. It might be because the frame was killed
// or navigated in between.
if (!info)
continue;
DCHECK(!info->client_uuid.empty());
// We can get info for a frame that was navigating end ended up with a
// different URL than expected. In such case, we should make sure to not
// expose cross-origin WindowClient.
if (info->url.GetOrigin() != controller->script_url().GetOrigin())
continue;
clients.push_back(std::move(info));
}
DidGetWindowClients(controller, std::move(options), std::move(callback),
std::move(clients));
}
// TODO(crbug.com/1199077): Update `sane_origin` to StorageKey once
// ServiceWorkerContainerHost implements StorageKey.
void DidGetExecutionReadyClient(
const base::WeakPtr<ServiceWorkerContextCore>& context,
const std::string& client_uuid,
const GURL& sane_origin,
NavigationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!context) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
nullptr /* client_info */);
return;
}
ServiceWorkerContainerHost* container_host =
context->GetContainerHostByClientID(client_uuid);
if (!container_host || !container_host->is_execution_ready()) {
// The page was destroyed before it became execution ready. Tell the
// renderer the page opened but it doesn't have access to it.
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk,
nullptr /* client_info */);
return;
}
CHECK_EQ(container_host->url().GetOrigin(), sane_origin);
blink::mojom::ServiceWorkerClientInfoPtr info = GetWindowClientInfo(
container_host->GetRenderFrameHostId(), container_host->create_time(),
container_host->client_uuid());
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk, std::move(info));
}
} // namespace
void FocusWindowClient(ServiceWorkerContainerHost* container_host,
ClientCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(container_host->IsContainerForWindowClient());
GlobalRenderFrameHostId rfh_id = container_host->GetRenderFrameHostId();
auto* render_frame_host = RenderFrameHostImpl::FromID(rfh_id);
WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(render_frame_host));
if (!render_frame_host || !web_contents) {
std::move(callback).Run(nullptr);
return;
}
// Avoid focusing on prerendered pages.
// TODO(https://crbug.com/1239553): Running the callback with nullptr
// results in NotFoundError whereas TypeError should be invoked
// according to the specification.
// https://w3c.github.io/ServiceWorker/#client-focus
if (render_frame_host->GetLifecycleState() ==
RenderFrameHost::LifecycleState::kPrerendering) {
std::move(callback).Run(nullptr);
return;
}
FrameTreeNode* frame_tree_node = render_frame_host->frame_tree_node();
// Focus the frame in the frame tree node, in case it has changed.
frame_tree_node->frame_tree()->SetFocusedFrame(
frame_tree_node, render_frame_host->GetSiteInstance());
// Focus the frame's view to make sure the frame is now considered as focused.
render_frame_host->GetView()->Focus();
// Move the web contents to the foreground.
web_contents->Activate();
blink::mojom::ServiceWorkerClientInfoPtr info = GetWindowClientInfo(
rfh_id, container_host->create_time(), container_host->client_uuid());
std::move(callback).Run(std::move(info));
}
void OpenWindow(const GURL& url,
const GURL& script_url,
const blink::StorageKey& key,
int worker_id,
int worker_process_id,
const base::WeakPtr<ServiceWorkerContextCore>& context,
WindowType type,
NavigationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderProcessHost* render_process_host =
RenderProcessHost::FromID(worker_process_id);
if (render_process_host->IsForGuestsOnly()) {
DidNavigate(context, script_url.GetOrigin(), key, std::move(callback),
GlobalRenderFrameHostId());
return;
}
scoped_refptr<ServiceWorkerContextWrapper> context_wrapper =
base::WrapRefCounted(context->wrapper());
SiteInstance* site_instance =
context_wrapper->process_manager()->GetSiteInstanceForWorker(worker_id);
if (!site_instance) {
// Worker isn't running anymore. Fail.
DidNavigate(context, script_url.GetOrigin(), key, std::move(callback),
GlobalRenderFrameHostId());
return;
}
// The following code is a rough copy of Navigator::RequestOpenURL. That
// function can't be used directly since there is no render frame host yet
// that the navigation will occur in.
OpenURLParams params(
url,
Referrer::SanitizeForRequest(
url, Referrer(script_url, network::mojom::ReferrerPolicy::kDefault)),
type == WindowType::PAYMENT_HANDLER_WINDOW
? WindowOpenDisposition::NEW_POPUP
: WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_AUTO_TOPLEVEL, true /* is_renderer_initiated */);
params.open_app_window_if_possible = type == WindowType::NEW_TAB_WINDOW;
params.initiator_origin = url::Origin::Create(script_url.GetOrigin());
// End of RequestOpenURL copy.
GetContentClient()->browser()->OpenURL(
site_instance, params,
base::BindOnce(&DidOpenURL, base::BindOnce(&DidNavigate, context,
script_url.GetOrigin(), key,
std::move(callback))));
}
void NavigateClient(const GURL& url,
const GURL& script_url,
const blink::StorageKey& key,
const GlobalRenderFrameHostId& rfh_id,
const base::WeakPtr<ServiceWorkerContextCore>& context,
NavigationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
RenderFrameHostImpl* rfhi = RenderFrameHostImpl::FromID(rfh_id);
WebContents* web_contents = WebContents::FromRenderFrameHost(rfhi);
if (!rfhi || !web_contents) {
DidNavigate(context, script_url.GetOrigin(), key, std::move(callback),
GlobalRenderFrameHostId());
return;
}
// Prerendered main frames are not allowed to navigate after their initial
// navigation. We can't proceed with the navigation and rely on the usual
// mechanism to disallow (PrerenderNavigationThrottle), because
// RequestOpenURL() crashes if called by a prerendering main frame.
if (rfhi->is_main_frame() && rfhi->frame_tree()->is_prerendering()) {
DidNavigate(context, script_url.GetOrigin(), key, std::move(callback),
GlobalRenderFrameHostId());
return;
}
// Reject the navigate() call if there is an ongoing browser-initiated
// navigation. Not rejecting it would allow websites to prevent the user from
// navigating away. See https://crbug.com/930154.
NavigationRequest* ongoing_navigation_request =
rfhi->frame_tree()->root()->navigation_request();
if (ongoing_navigation_request &&
ongoing_navigation_request->browser_initiated()) {
DidNavigate(context, script_url.GetOrigin(), key, std::move(callback),
GlobalRenderFrameHostId());
return;
}
int frame_tree_node_id = rfhi->frame_tree_node()->frame_tree_node_id();
Navigator& navigator = rfhi->frame_tree_node()->navigator();
navigator.RequestOpenURL(
rfhi, url, nullptr /* initiator_frame_token */,
ChildProcessHost::kInvalidUniqueID /* initiator_process_id */,
url::Origin::Create(script_url), nullptr /* post_body */,
std::string() /* extra_headers */,
Referrer::SanitizeForRequest(
url, Referrer(script_url, network::mojom::ReferrerPolicy::kDefault)),
WindowOpenDisposition::CURRENT_TAB,
false /* should_replace_current_entry */, false /* user_gesture */,
blink::mojom::TriggeringEventInfo::kUnknown,
std::string() /* href_translate */, nullptr /* blob_url_loader_factory */,
absl::nullopt);
new OpenURLObserver(
web_contents, frame_tree_node_id,
base::BindOnce(&DidNavigate, context, script_url.GetOrigin(), key,
std::move(callback)));
}
void GetClient(ServiceWorkerContainerHost* container_host,
ClientCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(container_host->IsContainerForClient());
blink::mojom::ServiceWorkerClientType host_client_type =
container_host->GetClientType();
if (host_client_type == blink::mojom::ServiceWorkerClientType::kWindow) {
blink::mojom::ServiceWorkerClientInfoPtr info = GetWindowClientInfo(
container_host->GetRenderFrameHostId(), container_host->create_time(),
container_host->client_uuid());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(info)));
return;
}
// TODO(dtapuska): Need to get frozen state for dedicated workers from
// DedicatedWorkerHost. crbug.com/968417
auto client_info = blink::mojom::ServiceWorkerClientInfo::New(
container_host->url(), blink::mojom::RequestContextFrameType::kNone,
container_host->client_uuid(), host_client_type,
/*page_hidden=*/true,
/*is_focused=*/false,
blink::mojom::ServiceWorkerClientLifecycleState::kActive,
base::TimeTicks(), container_host->create_time());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(client_info)));
}
void GetClients(const base::WeakPtr<ServiceWorkerVersion>& controller,
blink::mojom::ServiceWorkerClientQueryOptionsPtr options,
blink::mojom::ServiceWorkerHost::GetClientsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto clients = std::vector<blink::mojom::ServiceWorkerClientInfoPtr>();
if (!controller->HasControllee() && !options->include_uncontrolled) {
DidGetClients(std::move(callback), std::move(clients));
return;
}
// For Window clients we want to query the info on the UI thread first.
if (options->client_type == blink::mojom::ServiceWorkerClientType::kWindow ||
options->client_type == blink::mojom::ServiceWorkerClientType::kAll) {
GetWindowClients(controller, std::move(options), std::move(callback),
std::move(clients));
return;
}
GetNonWindowClients(controller, std::move(options), std::move(callback),
std::move(clients));
}
void DidNavigate(const base::WeakPtr<ServiceWorkerContextCore>& context,
const GURL& origin,
const blink::StorageKey& key,
NavigationCallback callback,
GlobalRenderFrameHostId rfh_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!context) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
nullptr /* client_info */);
return;
}
if (!rfh_id) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorFailed,
nullptr /* client_info */);
return;
}
for (std::unique_ptr<ServiceWorkerContextCore::ContainerHostIterator> it =
context->GetClientContainerHostIterator(
key, true /* include_reserved_clients */,
false /* include_back_forward_cached_clients */);
!it->IsAtEnd(); it->Advance()) {
ServiceWorkerContainerHost* container_host = it->GetContainerHost();
if (!container_host->IsContainerForWindowClient())
continue;
if (container_host->GetRenderFrameHostId() != rfh_id)
continue;
// DidNavigate must be called with a preparation complete client (the
// navigation was committed), but the client might not be execution ready
// yet (Blink hasn't yet created the Document).
DCHECK(container_host->is_response_committed());
if (!container_host->is_execution_ready()) {
container_host->AddExecutionReadyCallback(base::BindOnce(
&DidGetExecutionReadyClient, context, container_host->client_uuid(),
origin, std::move(callback)));
return;
}
DidGetExecutionReadyClient(context, container_host->client_uuid(), origin,
std::move(callback));
return;
}
// If here, it means that no container_host was found, in which case, the
// renderer should still be informed that the window was opened.
std::move(callback).Run(blink::ServiceWorkerStatusCode::kOk,
nullptr /* client_info */);
}
} // namespace service_worker_client_utils
} // namespace content