blob: 43c3f10a1053d9bf845a02a16474c101b5aa6d2d [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/bind.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner_helpers.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread_task_runner_handle.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_provider_impl.h"
#include "content/renderer/worker/worker_thread_registry.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.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 {
namespace {
void CreateSubresourceLoaderFactoryForProviderContext(
mojo::PendingRemote<blink::mojom::ServiceWorkerContainerHost>
remote_container_host,
mojo::PendingRemote<blink::mojom::ControllerServiceWorker>
remote_controller,
const std::string& client_id,
std::unique_ptr<network::PendingSharedURLLoaderFactory>
pending_fallback_factory,
mojo::PendingReceiver<blink::mojom::ControllerServiceWorkerConnector>
connector_receiver,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver,
scoped_refptr<base::SequencedTaskRunner> task_runner,
scoped_refptr<base::SequencedTaskRunner> worker_timing_callback_task_runner,
base::RepeatingCallback<
void(int, mojo::PendingReceiver<blink::mojom::WorkerTimingContainer>)>
worker_timing_callback) {
auto connector = base::MakeRefCounted<ControllerServiceWorkerConnector>(
std::move(remote_container_host), std::move(remote_controller),
client_id);
connector->AddBinding(std::move(connector_receiver));
ServiceWorkerSubresourceLoaderFactory::Create(
std::move(connector),
network::SharedURLLoaderFactory::Create(
std::move(pending_fallback_factory)),
std::move(receiver), std::move(task_runner),
std::move(worker_timing_callback_task_runner),
std::move(worker_timing_callback));
}
} // namespace
ServiceWorkerProviderContext::ServiceWorkerProviderContext(
blink::mojom::ServiceWorkerContainerType container_type,
mojo::PendingAssociatedReceiver<blink::mojom::ServiceWorkerContainer>
receiver,
mojo::PendingAssociatedRemote<blink::mojom::ServiceWorkerContainerHost>
host_remote,
blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory)
: container_type_(container_type),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
receiver_(this, std::move(receiver)),
fallback_loader_factory_(std::move(fallback_loader_factory)) {
if (host_remote.is_valid())
container_host_.Bind(std::move(host_remote));
// Set up the URL loader factory for sending subresource requests to
// the controller.
if (controller_info) {
SetController(std::move(controller_info),
false /* should_notify_controllerchange */);
}
}
ServiceWorkerProviderContext::~ServiceWorkerProviderContext() {
if (weak_wrapped_subresource_loader_factory_)
weak_wrapped_subresource_loader_factory_->Detach();
}
blink::mojom::ServiceWorkerObjectInfoPtr
ServiceWorkerProviderContext::TakeController() {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
return std::move(controller_);
}
int64_t ServiceWorkerProviderContext::GetControllerVersionId() const {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
return controller_version_id_;
}
blink::mojom::ControllerServiceWorkerMode
ServiceWorkerProviderContext::GetControllerServiceWorkerMode() const {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
return controller_mode_;
}
network::mojom::URLLoaderFactory*
ServiceWorkerProviderContext::GetSubresourceLoaderFactoryInternal() {
if (!remote_controller_ && !controller_connector_) {
// No controller is attached.
return nullptr;
}
if (controller_mode_ !=
blink::mojom::ControllerServiceWorkerMode::kControlled) {
// The controller does not exist or has no fetch event handler.
return nullptr;
}
if (!subresource_loader_factory_) {
DCHECK(!controller_connector_);
DCHECK(remote_controller_);
mojo::PendingRemote<blink::mojom::ServiceWorkerContainerHost>
remote_container_host = CloneRemoteContainerHost();
if (!remote_container_host)
return nullptr;
// Create a SubresourceLoaderFactory on a background thread to avoid
// extra contention on the main thread.
auto task_runner = base::CreateSequencedTaskRunner(
{base::ThreadPool(), base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&CreateSubresourceLoaderFactoryForProviderContext,
std::move(remote_container_host), std::move(remote_controller_),
client_id_, fallback_loader_factory_->Clone(),
controller_connector_.BindNewPipeAndPassReceiver(),
subresource_loader_factory_.BindNewPipeAndPassReceiver(),
task_runner, base::SequencedTaskRunnerHandle::Get(),
base::BindRepeating(
&ServiceWorkerProviderContext::AddPendingWorkerTimingReceiver,
weak_factory_.GetWeakPtr())));
DCHECK(!weak_wrapped_subresource_loader_factory_);
weak_wrapped_subresource_loader_factory_ =
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
subresource_loader_factory_.get());
}
return subresource_loader_factory_.get();
}
scoped_refptr<network::WeakWrapperSharedURLLoaderFactory>
ServiceWorkerProviderContext::GetSubresourceLoaderFactory() {
// If we can't get our internal factory it means the state is not currently
// good to process new requests regardless of the presence of an existing
// weak_wrapped_subresource_loader_factory.
if (!GetSubresourceLoaderFactoryInternal()) {
return nullptr;
}
return weak_wrapped_subresource_loader_factory_;
}
blink::mojom::ServiceWorkerContainerHost*
ServiceWorkerProviderContext::container_host() const {
DCHECK_EQ(blink::mojom::ServiceWorkerContainerType::kForWindow,
container_type_);
return container_host_ ? container_host_.get() : nullptr;
}
const std::set<blink::mojom::WebFeature>&
ServiceWorkerProviderContext::used_features() const {
return used_features_;
}
const std::string& ServiceWorkerProviderContext::client_id() const {
return client_id_;
}
const base::UnguessableToken&
ServiceWorkerProviderContext::fetch_request_window_id() const {
return fetch_request_window_id_;
}
void ServiceWorkerProviderContext::SetWebServiceWorkerProvider(
base::WeakPtr<WebServiceWorkerProviderImpl> provider) {
web_service_worker_provider_ = std::move(provider);
}
void ServiceWorkerProviderContext::RegisterWorkerClient(
mojo::PendingRemote<blink::mojom::ServiceWorkerWorkerClient>
pending_client) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
mojo::Remote<blink::mojom::ServiceWorkerWorkerClient> client(
std::move(pending_client));
client.set_disconnect_handler(base::BindOnce(
&ServiceWorkerProviderContext::UnregisterWorkerFetchContext,
base::Unretained(this), client.get()));
worker_clients_.push_back(std::move(client));
}
void ServiceWorkerProviderContext::CloneWorkerClientRegistry(
mojo::PendingReceiver<blink::mojom::ServiceWorkerWorkerClientRegistry>
receiver) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
worker_client_registry_receivers_.Add(this, std::move(receiver));
}
mojo::PendingRemote<blink::mojom::ServiceWorkerContainerHost>
ServiceWorkerProviderContext::CloneRemoteContainerHost() {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
if (!container_host_)
return mojo::NullRemote();
mojo::PendingRemote<blink::mojom::ServiceWorkerContainerHost>
remote_container_host;
container_host_->CloneContainerHost(
remote_container_host.InitWithNewPipeAndPassReceiver());
return remote_container_host;
}
void ServiceWorkerProviderContext::OnNetworkProviderDestroyed() {
container_host_.reset();
}
void ServiceWorkerProviderContext::DispatchNetworkQuiet() {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
if (controller_mode_ ==
blink::mojom::ControllerServiceWorkerMode::kNoController) {
return;
}
if (!container_host_)
return;
container_host_->HintToUpdateServiceWorker();
}
void ServiceWorkerProviderContext::NotifyExecutionReady() {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
DCHECK_EQ(container_type(),
blink::mojom::ServiceWorkerContainerType::kForWindow)
<< "only windows need to send this message; shared workers have "
"execution ready set on the browser-side when the response is "
"committed";
if (!container_host_)
return;
if (sent_execution_ready_) {
// Sometimes a new document can be created for a frame without a proper
// navigation, in cases like about:blank and javascript: URLs. In these
// cases the provider is not recreated and Blink can tell us that it's
// execution ready more than once. The browser-side host doesn't support
// changing the URL of the provider in these cases, so just ignore these
// notifications.
return;
}
sent_execution_ready_ = true;
container_host_->OnExecutionReady();
}
void ServiceWorkerProviderContext::AddPendingWorkerTimingReceiver(
int request_id,
mojo::PendingReceiver<blink::mojom::WorkerTimingContainer> receiver) {
// TODO(https://crbug.com/900700): Handle redirects properly. Currently on
// redirect, the receiver is replaced with a new one, discarding the timings
// before the redirect.
worker_timing_container_receivers_[request_id] = std::move(receiver);
}
mojo::PendingReceiver<blink::mojom::WorkerTimingContainer>
ServiceWorkerProviderContext::TakePendingWorkerTimingReceiver(int request_id) {
auto iter = worker_timing_container_receivers_.find(request_id);
if (iter == worker_timing_container_receivers_.end()) {
return mojo::NullReceiver();
}
auto worker_timing_receiver = std::move(iter->second);
worker_timing_container_receivers_.erase(iter);
return worker_timing_receiver;
}
void ServiceWorkerProviderContext::UnregisterWorkerFetchContext(
blink::mojom::ServiceWorkerWorkerClient* client) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
base::EraseIf(
worker_clients_,
[client](const mojo::Remote<blink::mojom::ServiceWorkerWorkerClient>&
remote_client) { return remote_client.get() == client; });
}
void ServiceWorkerProviderContext::SetController(
blink::mojom::ControllerServiceWorkerInfoPtr controller_info,
bool should_notify_controllerchange) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
controller_ = std::move(controller_info->object_info);
controller_version_id_ = controller_
? controller_->version_id
: blink::mojom::kInvalidServiceWorkerVersionId;
// The client id should never change once set.
DCHECK(client_id_.empty() || client_id_ == controller_info->client_id);
client_id_ = controller_info->client_id;
if (controller_info->fetch_request_window_id) {
DCHECK(controller_);
fetch_request_window_id_ = *controller_info->fetch_request_window_id;
} else {
fetch_request_window_id_ = base::UnguessableToken();
}
DCHECK((controller_info->mode ==
blink::mojom::ControllerServiceWorkerMode::kNoController &&
!controller_) ||
(controller_info->mode !=
blink::mojom::ControllerServiceWorkerMode::kNoController &&
controller_));
controller_mode_ = controller_info->mode;
remote_controller_ = std::move(controller_info->remote_controller);
// Propagate the controller to workers related to this provider.
if (controller_) {
DCHECK_NE(blink::mojom::kInvalidServiceWorkerVersionId,
controller_->version_id);
for (const auto& worker : worker_clients_) {
// This is a Mojo interface call to the (dedicated or shared) worker
// thread.
worker->OnControllerChanged(controller_mode_);
}
}
for (blink::mojom::WebFeature feature : controller_info->used_features)
used_features_.insert(feature);
// Reset connector state for subresource loader factory if necessary.
if (CanCreateSubresourceLoaderFactory()) {
// 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 (controller_connector_) {
// Used to have a controller at least once and have created a
// subresource loader factory before (if no subresource factory was
// created before, then the right controller, if any, will be used when
// the factory is created in GetSubresourceLoaderFactory, so there's
// nothing to do here).
// Update the connector's controller 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).
controller_connector_->UpdateController(std::move(remote_controller_));
}
}
// The WebServiceWorkerProviderImpl might not exist yet because the document
// has not yet been created (as WebServiceWorkerProviderImpl is created for a
// ServiceWorkerContainer). In that case, once it's created it will still get
// the controller from |this| via WebServiceWorkerProviderImpl::SetClient().
if (web_service_worker_provider_) {
web_service_worker_provider_->SetController(
std::move(controller_), used_features_, should_notify_controllerchange);
}
}
void ServiceWorkerProviderContext::PostMessageToClient(
blink::mojom::ServiceWorkerObjectInfoPtr source,
blink::TransferableMessage message) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
if (web_service_worker_provider_) {
web_service_worker_provider_->PostMessageToClient(std::move(source),
std::move(message));
}
}
void ServiceWorkerProviderContext::CountFeature(
blink::mojom::WebFeature feature) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
// ServiceWorkerProviderContext keeps track of features in order to propagate
// it to WebServiceWorkerProviderClient, which actually records the
// UseCounter.
used_features_.insert(feature);
if (web_service_worker_provider_) {
web_service_worker_provider_->CountFeature(feature);
}
}
bool ServiceWorkerProviderContext::CanCreateSubresourceLoaderFactory() const {
// |fallback_loader_factory| could be null in unit tests.
return fallback_loader_factory_ != nullptr;
}
void ServiceWorkerProviderContext::DestructOnMainThread() const {
if (!main_thread_task_runner_->RunsTasksInCurrentSequence() &&
main_thread_task_runner_->DeleteSoon(FROM_HERE, this)) {
return;
}
delete this;
}
} // namespace content