blob: 3a5b4c40da24432bc42da7ddfab581eda6526b2d [file] [log] [blame]
// Copyright 2013 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_provider_host.h"
#include <utility>
#include "base/callback_helpers.h"
#include "base/guid.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "content/browser/bad_message.h"
#include "content/browser/interface_provider_filtering.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_request_handler.h"
#include "content/browser/service_worker/service_worker_controllee_request_handler.h"
#include "content/browser/service_worker/service_worker_dispatcher_host.h"
#include "content/browser/service_worker/service_worker_handle.h"
#include "content/browser/service_worker/service_worker_registration_handle.h"
#include "content/browser/service_worker/service_worker_script_url_loader_factory.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/worker_interface_binders.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_util.h"
#include "content/public/common/resource_request_body.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "net/base/url_util.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "third_party/WebKit/common/message_port/message_port_channel.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/service_worker_object.mojom.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/service_worker_registration.mojom.h"
namespace content {
namespace {
const char kNoDocumentURLErrorMessage[] =
"No URL is associated with the caller's document.";
const char kShutdownErrorMessage[] = "The Service Worker system has shutdown.";
const char kUserDeniedPermissionMessage[] =
"The user denied permission to use Service Worker.";
const char kBadMessageInvalidURL[] = "Some URLs are invalid.";
const char kBadMessageInproperOrigins[] =
"Origins are not matching, or some cannot access service worker.";
const char kBadMessageFromNonWindow[] =
"The request message should not come from a non-window client.";
const char kBadMessageGetRegistrationForReadyDuplicated[] =
"There's already a completed or ongoing request to get the ready "
"registration.";
// Provider host for navigation with PlzNavigate or when service worker's
// context is created on the browser side. This function provides the next
// ServiceWorkerProviderHost ID for them, starts at -2 and keeps going down.
int NextBrowserProvidedProviderId() {
static int g_next_browser_provided_provider_id = -2;
return g_next_browser_provided_provider_id--;
}
// A request handler derivative used to handle navigation requests when
// skip_service_worker flag is set. It tracks the document URL and sets the url
// to the provider host.
class ServiceWorkerURLTrackingRequestHandler
: public ServiceWorkerRequestHandler {
public:
ServiceWorkerURLTrackingRequestHandler(
base::WeakPtr<ServiceWorkerContextCore> context,
base::WeakPtr<ServiceWorkerProviderHost> provider_host,
base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
ResourceType resource_type)
: ServiceWorkerRequestHandler(context,
provider_host,
blob_storage_context,
resource_type) {}
~ServiceWorkerURLTrackingRequestHandler() override {}
// Called via custom URLRequestJobFactory.
net::URLRequestJob* MaybeCreateJob(net::URLRequest* request,
net::NetworkDelegate*,
ResourceContext*) override {
// |provider_host_| may have been deleted when the request is resumed.
if (!provider_host_)
return nullptr;
const GURL stripped_url = net::SimplifyUrlForRequest(request->url());
provider_host_->SetDocumentUrl(stripped_url);
provider_host_->SetTopmostFrameUrl(request->site_for_cookies());
return nullptr;
}
void MaybeCreateLoader(const ResourceRequest& resource_request,
ResourceContext*,
LoaderCallback callback) override {
// |provider_host_| may have been deleted when the request is resumed.
if (!provider_host_)
return;
const GURL stripped_url = net::SimplifyUrlForRequest(resource_request.url);
provider_host_->SetDocumentUrl(stripped_url);
provider_host_->SetTopmostFrameUrl(resource_request.site_for_cookies);
// Fall back to network.
std::move(callback).Run(StartLoaderCallback());
}
private:
DISALLOW_COPY_AND_ASSIGN(ServiceWorkerURLTrackingRequestHandler);
};
void RemoveProviderHost(base::WeakPtr<ServiceWorkerContextCore> context,
int process_id,
int provider_id) {
// Temporary CHECK for debugging https://crbug.com/750267.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
TRACE_EVENT0("ServiceWorker",
"ServiceWorkerProviderHost::RemoveProviderHost");
if (!context || !context->GetProviderHost(process_id, provider_id)) {
// In some cancellation of navigation cases, it is possible for the
// pre-created host, whose |provider_id| is assigned by the browser process,
// to have been destroyed before being claimed by the renderer. The provider
// is then destroyed in the renderer, and no matching host will be found.
return;
}
context->RemoveProviderHost(process_id, provider_id);
}
WebContents* GetWebContents(int render_process_id, int render_frame_id) {
RenderFrameHost* rfh =
RenderFrameHost::FromID(render_process_id, render_frame_id);
return WebContents::FromRenderFrameHost(rfh);
}
void GetInterfaceImpl(const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe,
const url::Origin& origin,
int process_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto* process = RenderProcessHost::FromID(process_id);
if (!process)
return;
BindWorkerInterface(interface_name, std::move(interface_pipe), process,
origin);
}
} // anonymous namespace
// static
std::unique_ptr<ServiceWorkerProviderHost>
ServiceWorkerProviderHost::PreCreateNavigationHost(
base::WeakPtr<ServiceWorkerContextCore> context,
bool are_ancestors_secure,
const WebContentsGetter& web_contents_getter) {
CHECK(IsBrowserSideNavigationEnabled());
auto host = base::WrapUnique(new ServiceWorkerProviderHost(
ChildProcessHost::kInvalidUniqueID,
ServiceWorkerProviderHostInfo(
NextBrowserProvidedProviderId(), MSG_ROUTING_NONE,
SERVICE_WORKER_PROVIDER_FOR_WINDOW, are_ancestors_secure),
context, nullptr /* dispatcher_host */));
host->web_contents_getter_ = web_contents_getter;
return host;
}
// static
std::unique_ptr<ServiceWorkerProviderHost>
ServiceWorkerProviderHost::PreCreateForController(
base::WeakPtr<ServiceWorkerContextCore> context) {
auto host = base::WrapUnique(new ServiceWorkerProviderHost(
ChildProcessHost::kInvalidUniqueID,
ServiceWorkerProviderHostInfo(NextBrowserProvidedProviderId(),
MSG_ROUTING_NONE,
SERVICE_WORKER_PROVIDER_FOR_CONTROLLER,
true /* is_parent_frame_secure */),
std::move(context), nullptr));
return host;
}
// static
std::unique_ptr<ServiceWorkerProviderHost> ServiceWorkerProviderHost::Create(
int process_id,
ServiceWorkerProviderHostInfo info,
base::WeakPtr<ServiceWorkerContextCore> context,
base::WeakPtr<ServiceWorkerDispatcherHost> dispatcher_host) {
return base::WrapUnique(new ServiceWorkerProviderHost(
process_id, std::move(info), context, dispatcher_host));
}
ServiceWorkerProviderHost::ServiceWorkerProviderHost(
int render_process_id,
ServiceWorkerProviderHostInfo info,
base::WeakPtr<ServiceWorkerContextCore> context,
base::WeakPtr<ServiceWorkerDispatcherHost> dispatcher_host)
: client_uuid_(base::GenerateGUID()),
create_time_(base::TimeTicks::Now()),
render_process_id_(render_process_id),
render_thread_id_(kDocumentMainThreadId),
info_(std::move(info)),
context_(context),
dispatcher_host_(dispatcher_host),
allow_association_(true),
binding_(this),
interface_provider_binding_(this) {
DCHECK_NE(SERVICE_WORKER_PROVIDER_UNKNOWN, info_.type);
if (info_.type == SERVICE_WORKER_PROVIDER_FOR_CONTROLLER) {
// Actual |render_process_id| will be set after choosing a process for the
// controller, and |render_thread id| will be set when the service worker
// context gets started.
DCHECK_EQ(ChildProcessHost::kInvalidUniqueID, render_process_id);
render_thread_id_ = kInvalidEmbeddedWorkerThreadId;
} else {
// PlzNavigate
DCHECK(render_process_id != ChildProcessHost::kInvalidUniqueID ||
IsBrowserSideNavigationEnabled());
}
context_->RegisterProviderHostByClientID(client_uuid_, this);
// |client_| and |binding_| will be bound on CompleteNavigationInitialized
// (PlzNavigate) or on CompleteStartWorkerPreparation (providers for
// controller).
if (!info_.client_ptr_info.is_valid() && !info_.host_request.is_pending()) {
DCHECK(IsBrowserSideNavigationEnabled() ||
info_.type == SERVICE_WORKER_PROVIDER_FOR_CONTROLLER);
return;
}
container_.Bind(std::move(info_.client_ptr_info));
binding_.Bind(std::move(info_.host_request));
binding_.set_connection_error_handler(base::BindOnce(
&RemoveProviderHost, context_, render_process_id, info_.provider_id));
}
ServiceWorkerProviderHost::~ServiceWorkerProviderHost() {
// Temporary CHECK for debugging https://crbug.com/750267.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (context_)
context_->UnregisterProviderHostByClientID(client_uuid_);
// Clear docurl so the deferred activation of a waiting worker
// won't associate the new version with a provider being destroyed.
document_url_ = GURL();
if (controller_.get())
controller_->RemoveControllee(this);
RemoveAllMatchingRegistrations();
for (const GURL& pattern : associated_patterns_)
DecreaseProcessReference(pattern);
}
int ServiceWorkerProviderHost::frame_id() const {
if (info_.type == SERVICE_WORKER_PROVIDER_FOR_WINDOW)
return info_.route_id;
return MSG_ROUTING_NONE;
}
bool ServiceWorkerProviderHost::IsContextSecureForServiceWorker() const {
// |document_url_| may be empty if loading has not begun, or
// ServiceWorkerRequestHandler didn't handle the load (because e.g. another
// handler did first, or the initial request URL was such that
// OriginCanAccessServiceWorkers returned false).
if (!document_url_.is_valid())
return false;
if (!OriginCanAccessServiceWorkers(document_url_))
return false;
if (is_parent_frame_secure())
return true;
std::set<std::string> schemes;
GetContentClient()->browser()->GetSchemesBypassingSecureContextCheckWhitelist(
&schemes);
return schemes.find(document_url().scheme()) != schemes.end();
}
void ServiceWorkerProviderHost::OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
ChangedVersionAttributesMask changed_mask,
const ServiceWorkerRegistrationInfo& /* info */) {
if (!get_ready_callback_ || get_ready_callback_->is_null())
return;
if (changed_mask.active_changed() && registration->active_version()) {
// Wait until the state change so we don't send the get for ready
// registration complete message before set version attributes message.
registration->active_version()->RegisterStatusChangeCallback(base::BindOnce(
&ServiceWorkerProviderHost::ReturnRegistrationForReadyIfNeeded,
AsWeakPtr()));
}
}
void ServiceWorkerProviderHost::OnRegistrationFailed(
ServiceWorkerRegistration* registration) {
if (associated_registration_ == registration)
DisassociateRegistration();
RemoveMatchingRegistration(registration);
}
void ServiceWorkerProviderHost::OnRegistrationFinishedUninstalling(
ServiceWorkerRegistration* registration) {
RemoveMatchingRegistration(registration);
}
void ServiceWorkerProviderHost::OnSkippedWaiting(
ServiceWorkerRegistration* registration) {
if (associated_registration_ != registration)
return;
// A client is "using" a registration if it is controlled by the active
// worker of the registration. skipWaiting doesn't cause a client to start
// using the registration.
if (!controller_)
return;
ServiceWorkerVersion* active_version = registration->active_version();
DCHECK(active_version);
DCHECK_EQ(active_version->status(), ServiceWorkerVersion::ACTIVATING);
SetControllerVersionAttribute(active_version,
true /* notify_controllerchange */);
}
void ServiceWorkerProviderHost::SetDocumentUrl(const GURL& url) {
DCHECK(!url.has_ref());
document_url_ = url;
if (IsProviderForClient())
SyncMatchingRegistrations();
}
void ServiceWorkerProviderHost::SetTopmostFrameUrl(const GURL& url) {
topmost_frame_url_ = url;
}
void ServiceWorkerProviderHost::SetControllerVersionAttribute(
ServiceWorkerVersion* version,
bool notify_controllerchange) {
CHECK(!version || IsContextSecureForServiceWorker());
if (version == controller_.get())
return;
scoped_refptr<ServiceWorkerVersion> previous_version = controller_;
controller_ = version;
if (version)
version->AddControllee(this);
if (previous_version.get())
previous_version->RemoveControllee(this);
// SetController message should be sent only for controllees.
DCHECK(IsProviderForClient());
SendSetControllerServiceWorker(version, notify_controllerchange);
}
bool ServiceWorkerProviderHost::IsProviderForClient() const {
switch (info_.type) {
case SERVICE_WORKER_PROVIDER_FOR_WINDOW:
case SERVICE_WORKER_PROVIDER_FOR_WORKER:
case SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER:
return true;
case SERVICE_WORKER_PROVIDER_FOR_CONTROLLER:
return false;
case SERVICE_WORKER_PROVIDER_UNKNOWN:
NOTREACHED() << info_.type;
}
NOTREACHED() << info_.type;
return false;
}
blink::WebServiceWorkerClientType ServiceWorkerProviderHost::client_type()
const {
switch (info_.type) {
case SERVICE_WORKER_PROVIDER_FOR_WINDOW:
return blink::kWebServiceWorkerClientTypeWindow;
case SERVICE_WORKER_PROVIDER_FOR_WORKER:
return blink::kWebServiceWorkerClientTypeWorker;
case SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER:
return blink::kWebServiceWorkerClientTypeSharedWorker;
case SERVICE_WORKER_PROVIDER_FOR_CONTROLLER:
case SERVICE_WORKER_PROVIDER_UNKNOWN:
NOTREACHED() << info_.type;
}
NOTREACHED() << info_.type;
return blink::kWebServiceWorkerClientTypeWindow;
}
void ServiceWorkerProviderHost::AssociateRegistration(
ServiceWorkerRegistration* registration,
bool notify_controllerchange) {
CHECK(IsContextSecureForServiceWorker());
DCHECK(IsProviderForClient());
DCHECK(CanAssociateRegistration(registration));
associated_registration_ = registration;
AddMatchingRegistration(registration);
SetControllerVersionAttribute(registration->active_version(),
notify_controllerchange);
}
void ServiceWorkerProviderHost::DisassociateRegistration() {
DCHECK(IsProviderForClient());
queued_events_.clear();
if (!associated_registration_.get())
return;
associated_registration_ = nullptr;
SetControllerVersionAttribute(nullptr, false /* notify_controllerchange */);
}
void ServiceWorkerProviderHost::AddMatchingRegistration(
ServiceWorkerRegistration* registration) {
DCHECK(
ServiceWorkerUtils::ScopeMatches(registration->pattern(), document_url_));
if (!IsContextSecureForServiceWorker())
return;
size_t key = registration->pattern().spec().size();
if (base::ContainsKey(matching_registrations_, key))
return;
IncreaseProcessReference(registration->pattern());
registration->AddListener(this);
matching_registrations_[key] = registration;
ReturnRegistrationForReadyIfNeeded();
}
void ServiceWorkerProviderHost::RemoveMatchingRegistration(
ServiceWorkerRegistration* registration) {
size_t key = registration->pattern().spec().size();
DCHECK(base::ContainsKey(matching_registrations_, key));
DecreaseProcessReference(registration->pattern());
registration->RemoveListener(this);
matching_registrations_.erase(key);
}
ServiceWorkerRegistration*
ServiceWorkerProviderHost::MatchRegistration() const {
ServiceWorkerRegistrationMap::const_reverse_iterator it =
matching_registrations_.rbegin();
for (; it != matching_registrations_.rend(); ++it) {
if (it->second->is_uninstalled())
continue;
if (it->second->is_uninstalling())
return nullptr;
return it->second.get();
}
return nullptr;
}
void ServiceWorkerProviderHost::NotifyControllerLost() {
SetControllerVersionAttribute(nullptr, true /* notify_controllerchange */);
}
std::unique_ptr<ServiceWorkerRequestHandler>
ServiceWorkerProviderHost::CreateRequestHandler(
network::mojom::FetchRequestMode request_mode,
network::mojom::FetchCredentialsMode credentials_mode,
FetchRedirectMode redirect_mode,
const std::string& integrity,
bool keepalive,
ResourceType resource_type,
RequestContextType request_context_type,
RequestContextFrameType frame_type,
base::WeakPtr<storage::BlobStorageContext> blob_storage_context,
scoped_refptr<ResourceRequestBody> body,
bool skip_service_worker) {
// |skip_service_worker| is meant to apply to requests that could be handled
// by a service worker, as opposed to requests for the service worker script
// itself. So ignore it here for the service worker script and its imported
// scripts.
// TODO(falken): Really it should be treated as an error to set
// |skip_service_worker| for requests to start the service worker, but it's
// difficult to fix that renderer-side, since we don't know whether a request
// is for a service worker without access to IsHostToRunningServiceWorker() as
// that state is stored browser-side.
if (IsHostToRunningServiceWorker() &&
(resource_type == RESOURCE_TYPE_SERVICE_WORKER ||
resource_type == RESOURCE_TYPE_SCRIPT)) {
skip_service_worker = false;
}
if (skip_service_worker) {
if (!ServiceWorkerUtils::IsMainResourceType(resource_type))
return std::unique_ptr<ServiceWorkerRequestHandler>();
return std::make_unique<ServiceWorkerURLTrackingRequestHandler>(
context_, AsWeakPtr(), blob_storage_context, resource_type);
}
if (IsHostToRunningServiceWorker()) {
return std::make_unique<ServiceWorkerContextRequestHandler>(
context_, AsWeakPtr(), blob_storage_context, resource_type);
}
if (ServiceWorkerUtils::IsMainResourceType(resource_type) || controller()) {
return std::make_unique<ServiceWorkerControlleeRequestHandler>(
context_, AsWeakPtr(), blob_storage_context, request_mode,
credentials_mode, redirect_mode, integrity, keepalive, resource_type,
request_context_type, frame_type, body);
}
return std::unique_ptr<ServiceWorkerRequestHandler>();
}
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
ServiceWorkerProviderHost::CreateServiceWorkerRegistrationObjectInfo(
ServiceWorkerRegistration* registration) {
DCHECK(dispatcher_host_);
ServiceWorkerRegistrationHandle* existing_handle =
dispatcher_host_->FindServiceWorkerRegistrationHandle(provider_id(),
registration->id());
if (existing_handle) {
return existing_handle->CreateObjectInfo();
}
// ServiceWorkerRegistrationHandle ctor will register itself into
// |dispatcher_host_->registration_handles_|.
auto* new_handle = new ServiceWorkerRegistrationHandle(
context_, dispatcher_host_.get(), AsWeakPtr(), registration);
return new_handle->CreateObjectInfo();
}
blink::mojom::ServiceWorkerObjectInfoPtr
ServiceWorkerProviderHost::GetOrCreateServiceWorkerHandle(
ServiceWorkerVersion* version) {
DCHECK(dispatcher_host_);
if (!context_ || !version)
return blink::mojom::ServiceWorkerObjectInfo::New();
ServiceWorkerHandle* handle = dispatcher_host_->FindServiceWorkerHandle(
provider_id(), version->version_id());
if (handle) {
handle->IncrementRefCount();
return handle->CreateObjectInfo();
}
std::unique_ptr<ServiceWorkerHandle> new_handle(
ServiceWorkerHandle::Create(context_, AsWeakPtr(), version));
handle = new_handle.get();
dispatcher_host_->RegisterServiceWorkerHandle(std::move(new_handle));
return handle->CreateObjectInfo();
}
bool ServiceWorkerProviderHost::CanAssociateRegistration(
ServiceWorkerRegistration* registration) {
if (!context_)
return false;
if (running_hosted_version_.get())
return false;
if (!registration || associated_registration_.get() || !allow_association_)
return false;
return true;
}
void ServiceWorkerProviderHost::PostMessageToClient(
ServiceWorkerVersion* version,
const base::string16& message,
const std::vector<blink::MessagePortChannel>& sent_message_ports) {
DCHECK(IsProviderForClient());
if (!dispatcher_host_)
return;
auto message_pipes =
blink::MessagePortChannel::ReleaseHandles(sent_message_ports);
container_->PostMessageToClient(GetOrCreateServiceWorkerHandle(version),
message, std::move(message_pipes));
}
void ServiceWorkerProviderHost::CountFeature(uint32_t feature) {
if (!dispatcher_host_)
return;
// CountFeature message should be sent only for controllees.
DCHECK(IsProviderForClient());
Send(new ServiceWorkerMsg_CountFeature(render_thread_id_, provider_id(),
feature));
}
void ServiceWorkerProviderHost::AddScopedProcessReferenceToPattern(
const GURL& pattern) {
associated_patterns_.push_back(pattern);
IncreaseProcessReference(pattern);
}
void ServiceWorkerProviderHost::ClaimedByRegistration(
ServiceWorkerRegistration* registration) {
DCHECK(registration->active_version());
if (registration == associated_registration_) {
SetControllerVersionAttribute(registration->active_version(),
true /* notify_controllerchange */);
} else if (allow_association_) {
DisassociateRegistration();
AssociateRegistration(registration, true /* notify_controllerchange */);
}
}
std::unique_ptr<ServiceWorkerProviderHost>
ServiceWorkerProviderHost::PrepareForCrossSiteTransfer() {
DCHECK(!IsBrowserSideNavigationEnabled());
DCHECK(!ServiceWorkerUtils::IsServicificationEnabled());
DCHECK_NE(ChildProcessHost::kInvalidUniqueID, render_process_id_);
DCHECK_NE(MSG_ROUTING_NONE, info_.route_id);
DCHECK_EQ(kDocumentMainThreadId, render_thread_id_);
DCHECK_NE(SERVICE_WORKER_PROVIDER_UNKNOWN, info_.type);
// Clear the controller from the renderer-side provider, since no one knows
// what's going to happen until after cross-site transfer finishes.
if (controller_) {
SendSetControllerServiceWorker(nullptr,
false /* notify_controllerchange */);
}
std::unique_ptr<ServiceWorkerProviderHost> provisional_host =
base::WrapUnique(new ServiceWorkerProviderHost(
process_id(),
ServiceWorkerProviderHostInfo(std::move(info_), binding_.Unbind(),
container_.PassInterface()),
context_, dispatcher_host_));
for (const GURL& pattern : associated_patterns_)
DecreaseProcessReference(pattern);
RemoveAllMatchingRegistrations();
render_process_id_ = ChildProcessHost::kInvalidUniqueID;
render_thread_id_ = kInvalidEmbeddedWorkerThreadId;
dispatcher_host_ = nullptr;
return provisional_host;
}
void ServiceWorkerProviderHost::CompleteCrossSiteTransfer(
ServiceWorkerProviderHost* provisional_host) {
DCHECK(!IsBrowserSideNavigationEnabled());
DCHECK(!ServiceWorkerUtils::IsServicificationEnabled());
DCHECK_EQ(ChildProcessHost::kInvalidUniqueID, render_process_id_);
DCHECK_NE(ChildProcessHost::kInvalidUniqueID, provisional_host->process_id());
DCHECK_NE(MSG_ROUTING_NONE, provisional_host->frame_id());
render_process_id_ = provisional_host->process_id();
render_thread_id_ = kDocumentMainThreadId;
dispatcher_host_ = provisional_host->dispatcher_host()
? provisional_host->dispatcher_host()->AsWeakPtr()
: nullptr;
info_ = std::move(provisional_host->info_);
// Take the connection over from the provisional host.
DCHECK(!container_.is_bound());
DCHECK(!binding_.is_bound());
container_.Bind(provisional_host->container_.PassInterface());
binding_.Bind(provisional_host->binding_.Unbind());
binding_.set_connection_error_handler(
base::BindOnce(&RemoveProviderHost, context_,
provisional_host->process_id(), provider_id()));
for (const GURL& pattern : associated_patterns_)
IncreaseProcessReference(pattern);
SyncMatchingRegistrations();
// Now that the provider is stable and the connection is established,
// send it the SetController IPC if there is a controller.
if (controller_) {
SendSetControllerServiceWorker(controller_.get(),
false /* notify_controllerchange */);
}
}
// PlzNavigate
void ServiceWorkerProviderHost::CompleteNavigationInitialized(
int process_id,
ServiceWorkerProviderHostInfo info,
base::WeakPtr<ServiceWorkerDispatcherHost> dispatcher_host) {
CHECK(IsBrowserSideNavigationEnabled());
DCHECK_EQ(ChildProcessHost::kInvalidUniqueID, render_process_id_);
DCHECK_EQ(SERVICE_WORKER_PROVIDER_FOR_WINDOW, info_.type);
DCHECK_EQ(kDocumentMainThreadId, render_thread_id_);
DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id);
DCHECK_EQ(info_.provider_id, info.provider_id);
DCHECK_NE(MSG_ROUTING_NONE, info.route_id);
// Connect with the mojom::ServiceWorkerContainer on the renderer.
DCHECK(!container_.is_bound());
DCHECK(!binding_.is_bound());
container_.Bind(std::move(info.client_ptr_info));
binding_.Bind(std::move(info.host_request));
binding_.set_connection_error_handler(
base::BindOnce(&RemoveProviderHost, context_, process_id, provider_id()));
info_.route_id = info.route_id;
render_process_id_ = process_id;
dispatcher_host_ = dispatcher_host;
// Increase the references because the process which this provider host will
// host has been decided.
for (const GURL& pattern : associated_patterns_)
IncreaseProcessReference(pattern);
for (auto& key_registration : matching_registrations_)
IncreaseProcessReference(key_registration.second->pattern());
// Now that there is a connection with the renderer-side provider,
// send it the SetController IPC.
if (controller_) {
SendSetControllerServiceWorker(controller_.get(),
false /* notify_controllerchange */);
}
}
mojom::ServiceWorkerProviderInfoForStartWorkerPtr
ServiceWorkerProviderHost::CompleteStartWorkerPreparation(
int process_id,
scoped_refptr<ServiceWorkerVersion> hosted_version) {
DCHECK(context_);
DCHECK_EQ(kInvalidEmbeddedWorkerThreadId, render_thread_id_);
DCHECK_EQ(ChildProcessHost::kInvalidUniqueID, render_process_id_);
DCHECK_EQ(SERVICE_WORKER_PROVIDER_FOR_CONTROLLER, provider_type());
DCHECK(!running_hosted_version_);
DCHECK_NE(ChildProcessHost::kInvalidUniqueID, process_id);
running_hosted_version_ = std::move(hosted_version);
ServiceWorkerDispatcherHost* dispatcher_host =
context_->GetDispatcherHost(process_id);
DCHECK(dispatcher_host);
render_process_id_ = process_id;
dispatcher_host_ = dispatcher_host->AsWeakPtr();
// Retrieve the registration associated with |version|. The registration
// must be alive because the version keeps it during starting worker.
ServiceWorkerRegistration* registration = context_->GetLiveRegistration(
running_hosted_version()->registration_id());
DCHECK(registration);
// Initialize provider_info.
mojom::ServiceWorkerProviderInfoForStartWorkerPtr provider_info =
mojom::ServiceWorkerProviderInfoForStartWorker::New();
provider_info->provider_id = provider_id();
provider_info->registration =
CreateServiceWorkerRegistrationObjectInfo(registration);
provider_info->client_request = mojo::MakeRequest(&container_);
mojom::URLLoaderFactoryAssociatedPtrInfo script_loader_factory_ptr_info;
if (ServiceWorkerUtils::IsServicificationEnabled()) {
mojo::MakeStrongAssociatedBinding(
std::make_unique<ServiceWorkerScriptURLLoaderFactory>(
context_, AsWeakPtr(), context_->loader_factory_getter()),
mojo::MakeRequest(&script_loader_factory_ptr_info));
provider_info->script_loader_factory_ptr_info =
std::move(script_loader_factory_ptr_info);
}
binding_.Bind(mojo::MakeRequest(&provider_info->host_ptr_info));
binding_.set_connection_error_handler(
base::BindOnce(&RemoveProviderHost, context_, process_id, provider_id()));
interface_provider_binding_.Bind(FilterRendererExposedInterfaces(
mojom::kNavigation_ServiceWorkerSpec, process_id,
mojo::MakeRequest(&provider_info->interface_provider)));
// Set the document URL to the script url in order to allow
// register/unregister/getRegistration on ServiceWorkerGlobalScope.
SetDocumentUrl(running_hosted_version()->script_url());
return provider_info;
}
void ServiceWorkerProviderHost::SendUpdateFoundMessage(
int registration_handle_id) {
if (!dispatcher_host_)
return;
if (!IsReadyToSendMessages()) {
queued_events_.push_back(
base::Bind(&ServiceWorkerProviderHost::SendUpdateFoundMessage,
AsWeakPtr(), registration_handle_id));
return;
}
Send(new ServiceWorkerMsg_UpdateFound(
render_thread_id_, registration_handle_id));
}
void ServiceWorkerProviderHost::SendSetVersionAttributesMessage(
int registration_handle_id,
ChangedVersionAttributesMask changed_mask,
ServiceWorkerVersion* installing_version,
ServiceWorkerVersion* waiting_version,
ServiceWorkerVersion* active_version) {
if (!dispatcher_host_)
return;
if (!changed_mask.changed())
return;
if (!IsReadyToSendMessages()) {
queued_events_.push_back(base::Bind(
&ServiceWorkerProviderHost::SendSetVersionAttributesMessage,
AsWeakPtr(), registration_handle_id, changed_mask,
base::RetainedRef(installing_version),
base::RetainedRef(waiting_version), base::RetainedRef(active_version)));
return;
}
ServiceWorkerVersionAttributes attrs;
if (changed_mask.installing_changed())
attrs.installing = *GetOrCreateServiceWorkerHandle(installing_version);
if (changed_mask.waiting_changed())
attrs.waiting = *GetOrCreateServiceWorkerHandle(waiting_version);
if (changed_mask.active_changed())
attrs.active = *GetOrCreateServiceWorkerHandle(active_version);
Send(new ServiceWorkerMsg_SetVersionAttributes(
render_thread_id_, registration_handle_id, changed_mask.changed(),
attrs));
}
void ServiceWorkerProviderHost::SendServiceWorkerStateChangedMessage(
int worker_handle_id,
blink::mojom::ServiceWorkerState state) {
if (!dispatcher_host_)
return;
if (!IsReadyToSendMessages()) {
queued_events_.push_back(base::Bind(
&ServiceWorkerProviderHost::SendServiceWorkerStateChangedMessage,
AsWeakPtr(), worker_handle_id, state));
return;
}
Send(new ServiceWorkerMsg_ServiceWorkerStateChanged(render_thread_id_,
worker_handle_id, state));
}
void ServiceWorkerProviderHost::SetReadyToSendMessagesToWorker(
int render_thread_id) {
DCHECK(!IsReadyToSendMessages());
render_thread_id_ = render_thread_id;
for (const auto& event : queued_events_)
event.Run();
queued_events_.clear();
}
void ServiceWorkerProviderHost::SyncMatchingRegistrations() {
DCHECK(context_);
RemoveAllMatchingRegistrations();
const auto& registrations = context_->GetLiveRegistrations();
for (const auto& key_registration : registrations) {
ServiceWorkerRegistration* registration = key_registration.second;
if (!registration->is_uninstalled() &&
ServiceWorkerUtils::ScopeMatches(registration->pattern(),
document_url_))
AddMatchingRegistration(registration);
}
}
void ServiceWorkerProviderHost::RemoveAllMatchingRegistrations() {
for (const auto& it : matching_registrations_) {
ServiceWorkerRegistration* registration = it.second.get();
DecreaseProcessReference(registration->pattern());
registration->RemoveListener(this);
}
matching_registrations_.clear();
}
void ServiceWorkerProviderHost::IncreaseProcessReference(
const GURL& pattern) {
if (context_ && context_->process_manager()) {
context_->process_manager()->AddProcessReferenceToPattern(
pattern, render_process_id_);
}
}
void ServiceWorkerProviderHost::DecreaseProcessReference(
const GURL& pattern) {
if (context_ && context_->process_manager()) {
context_->process_manager()->RemoveProcessReferenceFromPattern(
pattern, render_process_id_);
}
}
void ServiceWorkerProviderHost::ReturnRegistrationForReadyIfNeeded() {
if (!get_ready_callback_ || get_ready_callback_->is_null())
return;
ServiceWorkerRegistration* registration = MatchRegistration();
if (!registration || !registration->active_version())
return;
TRACE_EVENT_ASYNC_END1("ServiceWorker",
"ServiceWorkerProviderHost::GetRegistrationForReady",
this, "Registration ID", registration->id());
if (!dispatcher_host_ || !IsContextAlive()) {
// Here no need to run or destroy |get_ready_callback_|, which will destroy
// together with |binding_| when |this| destroys.
return;
}
std::move(*get_ready_callback_)
.Run(CreateServiceWorkerRegistrationObjectInfo(registration));
}
bool ServiceWorkerProviderHost::IsReadyToSendMessages() const {
return render_thread_id_ != kInvalidEmbeddedWorkerThreadId;
}
bool ServiceWorkerProviderHost::IsContextAlive() {
return context_ != nullptr;
}
void ServiceWorkerProviderHost::Send(IPC::Message* message) const {
DCHECK(dispatcher_host_);
DCHECK(IsReadyToSendMessages());
dispatcher_host_->Send(message);
}
void ServiceWorkerProviderHost::SendSetControllerServiceWorker(
ServiceWorkerVersion* version,
bool notify_controllerchange) {
if (!dispatcher_host_)
return;
if (version) {
DCHECK(associated_registration_);
DCHECK_EQ(associated_registration_->active_version(), version);
DCHECK_EQ(controller_.get(), version);
}
std::vector<blink::mojom::WebFeature> used_features;
if (version) {
for (const uint32_t feature : version->used_features()) {
// TODO: version->used_features() should never have a feature outside the
// known feature range. But there is special case, see the details in
// crbug.com/758419.
if (feature <
static_cast<uint32_t>(blink::mojom::WebFeature::kNumberOfFeatures)) {
used_features.push_back(static_cast<blink::mojom::WebFeature>(feature));
}
}
}
container_->SetController(GetOrCreateServiceWorkerHandle(version),
used_features, notify_controllerchange);
}
void ServiceWorkerProviderHost::Register(
const GURL& script_url,
blink::mojom::ServiceWorkerRegistrationOptionsPtr options,
RegisterCallback callback) {
if (!CanServeContainerHostMethods(&callback, options->scope,
kServiceWorkerRegisterErrorPrefix,
nullptr)) {
return;
}
std::string error_message;
if (!IsValidRegisterMessage(script_url, *options, &error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
int64_t trace_id = base::TimeTicks::Now().since_origin().InMicroseconds();
TRACE_EVENT_ASYNC_BEGIN2(
"ServiceWorker", "ServiceWorkerProviderHost::Register", trace_id, "Scope",
options->scope.spec(), "Script URL", script_url.spec());
context_->RegisterServiceWorker(
script_url, *options, this,
base::AdaptCallbackForRepeating(
base::BindOnce(&ServiceWorkerProviderHost::RegistrationComplete,
AsWeakPtr(), std::move(callback), trace_id)));
}
void ServiceWorkerProviderHost::RegistrationComplete(
RegisterCallback callback,
int64_t trace_id,
ServiceWorkerStatusCode status,
const std::string& status_message,
int64_t registration_id) {
TRACE_EVENT_ASYNC_END2("ServiceWorker", "ServiceWorkerProviderHost::Register",
trace_id, "Status", status, "Registration ID",
registration_id);
if (!dispatcher_host_ || !IsContextAlive()) {
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(kServiceWorkerRegisterErrorPrefix) +
std::string(kShutdownErrorMessage),
nullptr);
return;
}
if (status != SERVICE_WORKER_OK) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, status_message,
&error_type, &error_message);
std::move(callback).Run(
error_type, kServiceWorkerRegisterErrorPrefix + error_message, nullptr);
return;
}
ServiceWorkerRegistration* registration =
context_->GetLiveRegistration(registration_id);
// ServiceWorkerRegisterJob calls its completion callback, which results in
// this function being called, while the registration is live.
DCHECK(registration);
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kNone, base::nullopt,
CreateServiceWorkerRegistrationObjectInfo(registration));
}
void ServiceWorkerProviderHost::GetRegistration(
const GURL& client_url,
GetRegistrationCallback callback) {
if (!CanServeContainerHostMethods(&callback, document_url(),
kServiceWorkerGetRegistrationErrorPrefix,
nullptr)) {
return;
}
std::string error_message;
if (!IsValidGetRegistrationMessage(client_url, &error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), nullptr);
return;
}
int64_t trace_id = base::TimeTicks::Now().since_origin().InMicroseconds();
TRACE_EVENT_ASYNC_BEGIN1("ServiceWorker",
"ServiceWorkerProviderHost::GetRegistration",
trace_id, "Client URL", client_url.spec());
context_->storage()->FindRegistrationForDocument(
client_url, base::AdaptCallbackForRepeating(base::BindOnce(
&ServiceWorkerProviderHost::GetRegistrationComplete,
AsWeakPtr(), std::move(callback), trace_id)));
}
void ServiceWorkerProviderHost::GetRegistrations(
GetRegistrationsCallback callback) {
if (!CanServeContainerHostMethods(&callback, document_url(),
kServiceWorkerGetRegistrationsErrorPrefix,
base::nullopt)) {
return;
}
std::string error_message;
if (!IsValidGetRegistrationsMessage(&error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(), base::nullopt);
return;
}
int64_t trace_id = base::TimeTicks::Now().since_origin().InMicroseconds();
TRACE_EVENT_ASYNC_BEGIN0(
"ServiceWorker", "ServiceWorkerProviderHost::GetRegistrations", trace_id);
context_->storage()->GetRegistrationsForOrigin(
document_url().GetOrigin(),
base::AdaptCallbackForRepeating(
base::BindOnce(&ServiceWorkerProviderHost::GetRegistrationsComplete,
AsWeakPtr(), std::move(callback), trace_id)));
}
void ServiceWorkerProviderHost::GetRegistrationComplete(
GetRegistrationCallback callback,
int64_t trace_id,
ServiceWorkerStatusCode status,
scoped_refptr<ServiceWorkerRegistration> registration) {
TRACE_EVENT_ASYNC_END2(
"ServiceWorker", "ServiceWorkerProviderHost::GetRegistration", trace_id,
"Status", status, "Registration ID",
registration ? registration->id()
: blink::mojom::kInvalidServiceWorkerRegistrationId);
if (!dispatcher_host_ || !IsContextAlive()) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(kServiceWorkerGetRegistrationErrorPrefix) +
std::string(kShutdownErrorMessage),
nullptr);
return;
}
if (status != SERVICE_WORKER_OK && status != SERVICE_WORKER_ERROR_NOT_FOUND) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, std::string(), &error_type,
&error_message);
std::move(callback).Run(
error_type, kServiceWorkerGetRegistrationErrorPrefix + error_message,
nullptr);
return;
}
DCHECK(status != SERVICE_WORKER_OK || registration);
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info;
if (status == SERVICE_WORKER_OK && !registration->is_uninstalling())
info = CreateServiceWorkerRegistrationObjectInfo(registration.get());
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt, std::move(info));
}
void ServiceWorkerProviderHost::GetRegistrationsComplete(
GetRegistrationsCallback callback,
int64_t trace_id,
ServiceWorkerStatusCode status,
const std::vector<scoped_refptr<ServiceWorkerRegistration>>&
registrations) {
TRACE_EVENT_ASYNC_END1("ServiceWorker",
"ServiceWorkerProviderHost::GetRegistrations",
trace_id, "Status", status);
if (!dispatcher_host_ || !IsContextAlive()) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(kServiceWorkerGetRegistrationsErrorPrefix) +
std::string(kShutdownErrorMessage),
base::nullopt);
return;
}
if (status != SERVICE_WORKER_OK) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, std::string(), &error_type,
&error_message);
std::move(callback).Run(
error_type, kServiceWorkerGetRegistrationsErrorPrefix + error_message,
base::nullopt);
return;
}
std::vector<blink::mojom::ServiceWorkerRegistrationObjectInfoPtr>
object_infos;
for (const auto& registration : registrations) {
DCHECK(registration.get());
if (!registration->is_uninstalling()) {
object_infos.push_back(
CreateServiceWorkerRegistrationObjectInfo(registration.get()));
}
}
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt, std::move(object_infos));
}
void ServiceWorkerProviderHost::GetRegistrationForReady(
GetRegistrationForReadyCallback callback) {
std::string error_message;
if (!IsValidGetRegistrationForReadyMessage(&error_message)) {
mojo::ReportBadMessage(error_message);
// ReportBadMessage() will kill the renderer process, but Mojo complains if
// the callback is not run. Just run it with nonsense arguments.
std::move(callback).Run(nullptr);
return;
}
TRACE_EVENT_ASYNC_BEGIN0("ServiceWorker",
"ServiceWorkerProviderHost::GetRegistrationForReady",
this);
DCHECK(!get_ready_callback_);
get_ready_callback_ =
std::make_unique<GetRegistrationForReadyCallback>(std::move(callback));
ReturnRegistrationForReadyIfNeeded();
}
void ServiceWorkerProviderHost::GetControllerServiceWorker(
mojom::ControllerServiceWorkerRequest controller_request) {
// TODO(kinuko): Log the reasons we drop the request.
if (!dispatcher_host_ || !IsContextAlive() || !controller_)
return;
// TODO(kinuko): Call version_->StartWorker() here if the service
// is stopped. Currently it should be starting or running at this point.
DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
DCHECK(controller_->running_status() == EmbeddedWorkerStatus::STARTING ||
controller_->running_status() == EmbeddedWorkerStatus::RUNNING);
controller_->controller()->Clone(std::move(controller_request));
}
void ServiceWorkerProviderHost::CloneForWorker(
mojom::ServiceWorkerContainerHostRequest container_host_request) {
DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
bindings_for_worker_threads_.AddBinding(this,
std::move(container_host_request));
}
bool ServiceWorkerProviderHost::IsValidRegisterMessage(
const GURL& script_url,
const blink::mojom::ServiceWorkerRegistrationOptions& options,
std::string* out_error) const {
if (client_type() != blink::kWebServiceWorkerClientTypeWindow) {
*out_error = kBadMessageFromNonWindow;
return false;
}
if (!options.scope.is_valid() || !script_url.is_valid()) {
*out_error = kBadMessageInvalidURL;
return false;
}
if (ServiceWorkerUtils::ContainsDisallowedCharacter(options.scope, script_url,
out_error)) {
return false;
}
std::vector<GURL> urls = {document_url(), options.scope, script_url};
if (!ServiceWorkerUtils::AllOriginsMatchAndCanAccessServiceWorkers(urls)) {
*out_error = kBadMessageInproperOrigins;
return false;
}
return true;
}
bool ServiceWorkerProviderHost::IsValidGetRegistrationMessage(
const GURL& client_url,
std::string* out_error) const {
if (client_type() != blink::kWebServiceWorkerClientTypeWindow) {
*out_error = kBadMessageFromNonWindow;
return false;
}
if (!client_url.is_valid()) {
*out_error = kBadMessageInvalidURL;
return false;
}
std::vector<GURL> urls = {document_url(), client_url};
if (!ServiceWorkerUtils::AllOriginsMatchAndCanAccessServiceWorkers(urls)) {
*out_error = kBadMessageInproperOrigins;
return false;
}
return true;
}
bool ServiceWorkerProviderHost::IsValidGetRegistrationsMessage(
std::string* out_error) const {
if (client_type() != blink::kWebServiceWorkerClientTypeWindow) {
*out_error = kBadMessageFromNonWindow;
return false;
}
if (!OriginCanAccessServiceWorkers(document_url())) {
*out_error = kBadMessageInproperOrigins;
return false;
}
return true;
}
bool ServiceWorkerProviderHost::IsValidGetRegistrationForReadyMessage(
std::string* out_error) const {
if (client_type() != blink::kWebServiceWorkerClientTypeWindow) {
*out_error = kBadMessageFromNonWindow;
return false;
}
if (get_ready_callback_) {
*out_error = kBadMessageGetRegistrationForReadyDuplicated;
return false;
}
return true;
}
void ServiceWorkerProviderHost::GetInterface(
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK_NE(kDocumentMainThreadId, render_thread_id_);
DCHECK(IsHostToRunningServiceWorker());
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(
&GetInterfaceImpl, interface_name, std::move(interface_pipe),
running_hosted_version_->script_origin(), render_process_id_));
}
template <typename CallbackType, typename... Args>
bool ServiceWorkerProviderHost::CanServeContainerHostMethods(
CallbackType* callback,
const GURL& scope,
const char* error_prefix,
Args... args) {
if (!dispatcher_host_ || !IsContextAlive()) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(error_prefix) + std::string(kShutdownErrorMessage),
args...);
return false;
}
// TODO(falken): This check can be removed once crbug.com/439697 is fixed.
// (Also see crbug.com/776408)
if (document_url().is_empty()) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kSecurity,
std::string(error_prefix) + std::string(kNoDocumentURLErrorMessage),
args...);
return false;
}
if (!GetContentClient()->browser()->AllowServiceWorker(
scope, topmost_frame_url(), dispatcher_host_->resource_context(),
base::Bind(&GetWebContents, render_process_id_, frame_id()))) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kDisabled,
std::string(error_prefix) + std::string(kUserDeniedPermissionMessage),
args...);
return false;
}
return true;
}
} // namespace content