blob: 610e4b4f3d3ad81cd7ecef2041f7c0a96e5f47b6 [file] [log] [blame]
// Copyright 2014 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/renderer/service_worker/service_worker_provider_context.h"
#include <set>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/stl_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/child/child_thread_impl.h"
#include "content/child/thread_safe_sender.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/service_names.mojom.h"
#include "content/renderer/service_worker/controller_service_worker_connector.h"
#include "content/renderer/service_worker/service_worker_subresource_loader.h"
#include "content/renderer/service_worker/web_service_worker_impl.h"
#include "content/renderer/service_worker/web_service_worker_registration_impl.h"
#include "content/renderer/worker_thread_registry.h"
#include "mojo/public/cpp/bindings/strong_associated_binding.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
namespace content {
// Holds state for service worker clients.
struct ServiceWorkerProviderContext::ProviderStateForClient {
explicit ProviderStateForClient(
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory)
: fallback_loader_factory(std::move(fallback_loader_factory)) {}
~ProviderStateForClient() = default;
// |controller| will be set by SetController() and taken by TakeController().
blink::mojom::ServiceWorkerObjectInfoPtr controller;
// Keeps version id of the current controller service worker object.
int64_t controller_version_id = blink::mojom::kInvalidServiceWorkerVersionId;
// S13nServiceWorker:
// Used to intercept requests from the controllee and dispatch them
// as events to the controller ServiceWorker.
network::mojom::URLLoaderFactoryPtr subresource_loader_factory;
// S13nServiceWorker:
// Used when we create |subresource_loader_factory|.
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory;
// S13nServiceWorker:
// The Client#id value of the client.
std::string client_id;
blink::mojom::ControllerServiceWorkerMode controller_mode =
blink::mojom::ControllerServiceWorkerMode::kNoController;
// Tracks feature usage for UseCounter.
std::set<blink::mojom::WebFeature> used_features;
// Corresponds to a ServiceWorkerContainer. We notify it when
// ServiceWorkerContainer#controller should be changed.
base::WeakPtr<WebServiceWorkerProviderImpl> web_service_worker_provider;
// Keeps ServiceWorkerWorkerClient pointers of dedicated or shared workers
// which are associated with the ServiceWorkerProviderContext.
// - If this ServiceWorkerProviderContext is for a Document, then
// |worker_clients| contains all its dedicated workers.
// - If this ServiceWorkerProviderContext is for a SharedWorker (technically
// speaking, for its shadow page), then |worker_clients| has one element:
// the shared worker.
std::vector<mojom::ServiceWorkerWorkerClientPtr> worker_clients;
// For adding new ServiceWorkerWorkerClients.
mojo::BindingSet<mojom::ServiceWorkerWorkerClientRegistry>
worker_client_registry_bindings;
// S13nServiceWorker
// Used in |subresource_loader_factory| to get the connection to the
// controller service worker. Kept here in order to call
// OnContainerHostConnectionClosed when container_host_ for the
// provider is reset.
// This is (re)set to nullptr if no controller is attached to this client.
scoped_refptr<ControllerServiceWorkerConnector> controller_connector;
// For service worker clients. Map from registration id to JavaScript
// ServiceWorkerRegistration object.
std::map<int64_t, WebServiceWorkerRegistrationImpl*> registrations_;
// For service worker clients. Map from version id to JavaScript ServiceWorker
// object.
std::map<int64_t, WebServiceWorkerImpl*> workers_;
};
// For service worker clients.
ServiceWorkerProviderContext::ServiceWorkerProviderContext(
int provider_id,
blink::mojom::ServiceWorkerProviderType provider_type,
mojom::ServiceWorkerContainerAssociatedRequest request,
mojom::ServiceWorkerContainerHostAssociatedPtrInfo host_ptr_info,
mojom::ControllerServiceWorkerInfoPtr controller_info,
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory)
: provider_type_(provider_type),
provider_id_(provider_id),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
binding_(this, std::move(request)),
weak_factory_(this) {
container_host_.Bind(std::move(host_ptr_info));
state_for_client_ = std::make_unique<ProviderStateForClient>(
std::move(fallback_loader_factory));
// Set up the URL loader factory for sending subresource requests to
// the controller.
if (controller_info) {
SetController(std::move(controller_info),
std::vector<blink::mojom::WebFeature>(),
false /* should_notify_controllerchange */);
}
}
// For service worker execution contexts.
ServiceWorkerProviderContext::ServiceWorkerProviderContext(
int provider_id,
mojom::ServiceWorkerContainerAssociatedRequest request,
mojom::ServiceWorkerContainerHostAssociatedPtrInfo host_ptr_info)
: provider_type_(
blink::mojom::ServiceWorkerProviderType::kForServiceWorker),
provider_id_(provider_id),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
binding_(this, std::move(request)),
weak_factory_(this) {
container_host_.Bind(std::move(host_ptr_info));
}
ServiceWorkerProviderContext::~ServiceWorkerProviderContext() = default;
blink::mojom::ServiceWorkerObjectInfoPtr
ServiceWorkerProviderContext::TakeController() {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
return std::move(state_for_client_->controller);
}
int64_t ServiceWorkerProviderContext::GetControllerVersionId() const {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
return state_for_client_->controller_version_id;
}
blink::mojom::ControllerServiceWorkerMode
ServiceWorkerProviderContext::IsControlledByServiceWorker() const {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
return state_for_client_->controller_mode;
}
network::mojom::URLLoaderFactory*
ServiceWorkerProviderContext::GetSubresourceLoaderFactory() {
if (!ServiceWorkerUtils::IsServicificationEnabled())
return nullptr;
DCHECK(state_for_client_);
auto* state = state_for_client_.get();
if (!state->controller_connector ||
state->controller_connector->state() ==
ControllerServiceWorkerConnector::State::kNoController) {
// No controller is attached.
return nullptr;
}
if (state->controller_mode !=
blink::mojom::ControllerServiceWorkerMode::kControlled) {
// The controller does not exist or has no fetch event handler.
return nullptr;
}
if (!state->subresource_loader_factory) {
ServiceWorkerSubresourceLoaderFactory::Create(
state->controller_connector, state->fallback_loader_factory,
mojo::MakeRequest(&state->subresource_loader_factory));
}
return state->subresource_loader_factory.get();
}
mojom::ServiceWorkerContainerHost*
ServiceWorkerProviderContext::container_host() const {
DCHECK_EQ(blink::mojom::ServiceWorkerProviderType::kForWindow,
provider_type_);
return container_host_.get();
}
const std::set<blink::mojom::WebFeature>&
ServiceWorkerProviderContext::used_features() const {
DCHECK(state_for_client_);
return state_for_client_->used_features;
}
const std::string& ServiceWorkerProviderContext::client_id() const {
DCHECK(state_for_client_);
return state_for_client_->client_id;
}
void ServiceWorkerProviderContext::SetWebServiceWorkerProvider(
base::WeakPtr<WebServiceWorkerProviderImpl> provider) {
DCHECK(state_for_client_);
state_for_client_->web_service_worker_provider = provider;
}
void ServiceWorkerProviderContext::RegisterWorkerClient(
mojom::ServiceWorkerWorkerClientPtr client) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
client.set_connection_error_handler(base::BindOnce(
&ServiceWorkerProviderContext::UnregisterWorkerFetchContext,
base::Unretained(this), client.get()));
state_for_client_->worker_clients.push_back(std::move(client));
}
void ServiceWorkerProviderContext::CloneWorkerClientRegistry(
mojom::ServiceWorkerWorkerClientRegistryRequest request) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
state_for_client_->worker_client_registry_bindings.AddBinding(
this, std::move(request));
}
mojom::ServiceWorkerContainerHostPtrInfo
ServiceWorkerProviderContext::CloneContainerHostPtrInfo() {
DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
mojom::ServiceWorkerContainerHostPtrInfo container_host_ptr_info;
container_host_->CloneForWorker(mojo::MakeRequest(&container_host_ptr_info));
return container_host_ptr_info;
}
scoped_refptr<WebServiceWorkerRegistrationImpl>
ServiceWorkerProviderContext::GetOrCreateServiceWorkerRegistrationObject(
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr info) {
DCHECK_EQ(blink::mojom::ServiceWorkerProviderType::kForWindow,
provider_type_);
DCHECK(state_for_client_);
auto found = state_for_client_->registrations_.find(info->registration_id);
if (found != state_for_client_->registrations_.end()) {
found->second->AttachForServiceWorkerClient(std::move(info));
return found->second;
}
return WebServiceWorkerRegistrationImpl::CreateForServiceWorkerClient(
std::move(info), weak_factory_.GetWeakPtr());
}
scoped_refptr<WebServiceWorkerImpl>
ServiceWorkerProviderContext::GetOrCreateServiceWorkerObject(
blink::mojom::ServiceWorkerObjectInfoPtr info) {
DCHECK_EQ(blink::mojom::ServiceWorkerProviderType::kForWindow,
provider_type_);
DCHECK(state_for_client_);
if (!info)
return nullptr;
auto found = state_for_client_->workers_.find(info->version_id);
if (found != state_for_client_->workers_.end()) {
return found->second;
}
return WebServiceWorkerImpl::CreateForServiceWorkerClient(
std::move(info), weak_factory_.GetWeakPtr());
}
void ServiceWorkerProviderContext::OnNetworkProviderDestroyed() {
container_host_.reset();
if (state_for_client_ && state_for_client_->controller_connector)
state_for_client_->controller_connector->OnContainerHostConnectionClosed();
}
void ServiceWorkerProviderContext::PingContainerHost(
base::OnceClosure callback) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
container_host_->Ping(std::move(callback));
}
void ServiceWorkerProviderContext::UnregisterWorkerFetchContext(
mojom::ServiceWorkerWorkerClient* client) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
base::EraseIf(
state_for_client_->worker_clients,
[client](const mojom::ServiceWorkerWorkerClientPtr& client_ptr) {
return client_ptr.get() == client;
});
}
void ServiceWorkerProviderContext::SetController(
mojom::ControllerServiceWorkerInfoPtr controller_info,
const std::vector<blink::mojom::WebFeature>& used_features,
bool should_notify_controllerchange) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
ProviderStateForClient* state = state_for_client_.get();
DCHECK(state);
state->controller = std::move(controller_info->object_info);
state->controller_version_id =
state->controller ? state->controller->version_id
: blink::mojom::kInvalidServiceWorkerVersionId;
// The client id should never change once set.
DCHECK(state->client_id.empty() ||
state->client_id == controller_info->client_id);
state->client_id = controller_info->client_id;
DCHECK((controller_info->mode ==
blink::mojom::ControllerServiceWorkerMode::kNoController &&
!state->controller) ||
(controller_info->mode !=
blink::mojom::ControllerServiceWorkerMode::kNoController &&
state->controller));
state->controller_mode = controller_info->mode;
// Propagate the controller to workers related to this provider.
if (state->controller) {
DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId,
state->controller->version_id);
for (const auto& worker : state->worker_clients) {
// This is a Mojo interface call to the (dedicated or shared) worker
// thread.
worker->OnControllerChanged(state->controller_mode);
}
}
for (blink::mojom::WebFeature feature : used_features)
state->used_features.insert(feature);
// S13nServiceWorker:
// Reset subresource loader factory if necessary.
if (CanCreateSubresourceLoaderFactory()) {
DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
// There could be four patterns:
// (A) Had a controller, and got a new controller.
// (B) Had a controller, and lost the controller.
// (C) Didn't have a controller, and got a new controller.
// (D) Didn't have a controller, and lost the controller (nothing to do).
if (state->controller_connector) {
// Used to have a controller at least once.
// Reset the existing connector so that subsequent resource requests
// will get the new controller in case (A)/(C), or fallback to the
// network in case (B). Inflight requests that are already dispatched may
// just use the existing controller or may use the new controller
// settings depending on when the request is actually passed to the
// factory (this part is inherently racy).
state->controller_connector->ResetControllerConnection(
mojom::ControllerServiceWorkerPtr(
std::move(controller_info->endpoint)));
} else if (state->controller) {
// Case (C): never had a controller, but got a new one now.
// Set a new |state->controller_connector| so that subsequent resource
// requests will see it.
mojom::ControllerServiceWorkerPtr controller_ptr(
std::move(controller_info->endpoint));
state->controller_connector =
base::MakeRefCounted<ControllerServiceWorkerConnector>(
container_host_.get(), std::move(controller_ptr),
controller_info->client_id);
}
}
// The WebServiceWorkerProviderImpl might not exist yet because the document
// has not yet been created (as WebServiceWorkerImpl is created for a
// ServiceWorkerContainer). In that case, once it's created it will still get
// the controller from |this| via WebServiceWorkerProviderImpl::SetClient().
if (state->web_service_worker_provider) {
state->web_service_worker_provider->SetController(
std::move(state->controller), state->used_features,
should_notify_controllerchange);
}
}
void ServiceWorkerProviderContext::PostMessageToClient(
blink::mojom::ServiceWorkerObjectInfoPtr source,
blink::TransferableMessage message) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
ProviderStateForClient* state = state_for_client_.get();
DCHECK(state);
if (state->web_service_worker_provider) {
state->web_service_worker_provider->PostMessageToClient(std::move(source),
std::move(message));
}
}
void ServiceWorkerProviderContext::AddServiceWorkerRegistrationObject(
int64_t registration_id,
WebServiceWorkerRegistrationImpl* registration) {
DCHECK(state_for_client_);
DCHECK(
!base::ContainsKey(state_for_client_->registrations_, registration_id));
state_for_client_->registrations_[registration_id] = registration;
}
void ServiceWorkerProviderContext::RemoveServiceWorkerRegistrationObject(
int64_t registration_id) {
DCHECK(state_for_client_);
DCHECK(base::ContainsKey(state_for_client_->registrations_, registration_id));
state_for_client_->registrations_.erase(registration_id);
}
bool ServiceWorkerProviderContext::
ContainsServiceWorkerRegistrationObjectForTesting(int64_t registration_id) {
DCHECK(state_for_client_);
return base::ContainsKey(state_for_client_->registrations_, registration_id);
}
void ServiceWorkerProviderContext::AddServiceWorkerObject(
int64_t version_id,
WebServiceWorkerImpl* worker) {
DCHECK(state_for_client_);
DCHECK(!base::ContainsKey(state_for_client_->workers_, version_id));
state_for_client_->workers_[version_id] = worker;
}
void ServiceWorkerProviderContext::RemoveServiceWorkerObject(
int64_t version_id) {
DCHECK(state_for_client_);
DCHECK(base::ContainsKey(state_for_client_->workers_, version_id));
state_for_client_->workers_.erase(version_id);
}
bool ServiceWorkerProviderContext::ContainsServiceWorkerObjectForTesting(
int64_t version_id) {
DCHECK(state_for_client_);
return base::ContainsKey(state_for_client_->workers_, version_id);
}
void ServiceWorkerProviderContext::CountFeature(
blink::mojom::WebFeature feature) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK(state_for_client_);
ProviderStateForClient* state = state_for_client_.get();
// ServiceWorkerProviderContext keeps track of features in order to propagate
// it to WebServiceWorkerProviderClient, which actually records the
// UseCounter.
state->used_features.insert(feature);
if (state->web_service_worker_provider) {
state->web_service_worker_provider->CountFeature(feature);
}
}
bool ServiceWorkerProviderContext::CanCreateSubresourceLoaderFactory() const {
// Expected that it is called only for clients.
DCHECK(state_for_client_);
// |state_for_client_->fallback_loader_factory| could be null in unit tests.
return (ServiceWorkerUtils::IsServicificationEnabled() &&
state_for_client_->fallback_loader_factory);
}
void ServiceWorkerProviderContext::DestructOnMainThread() const {
if (!main_thread_task_runner_->RunsTasksInCurrentSequence() &&
main_thread_task_runner_->DeleteSoon(FROM_HERE, this)) {
return;
}
delete this;
}
} // namespace content