blob: 49dfdfcc5b5b538c053e494eec491570a3e814e5 [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_network_provider.h"
#include "base/atomic_sequence_num.h"
#include "base/single_thread_task_runner.h"
#include "content/common/navigation_params.h"
#include "content/common/service_worker/service_worker_provider.mojom.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/origin_util.h"
#include "content/renderer/loader/request_extra_data.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/renderer_blink_platform_impl.h"
#include "ipc/ipc_sync_channel.h"
#include "mojo/public/cpp/bindings/associated_group.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "third_party/blink/public/common/frame/sandbox_flags.h"
#include "third_party/blink/public/common/service_worker/service_worker_utils.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom.h"
#include "third_party/blink/public/platform/modules/service_worker/web_service_worker_network_provider.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace content {
namespace {
// Must be unique in the child process.
int GetNextProviderId() {
static base::AtomicSequenceNumber sequence;
return sequence.GetNext(); // We start at zero.
}
// Returns whether it's possible for a document whose frame is a descendant of
// |frame| to be a secure context, not considering scheme exceptions (since any
// document can be a secure context if it has a scheme exception). See
// Document::isSecureContextImpl for more details.
bool IsFrameSecure(blink::WebFrame* frame) {
while (frame) {
if (!frame->GetSecurityOrigin().IsPotentiallyTrustworthy())
return false;
frame = frame->Parent();
}
return true;
}
// An WebServiceWorkerNetworkProvider for frame. This wraps
// ServiceWorkerNetworkProvider implementation and is owned by blink.
class WebServiceWorkerNetworkProviderForFrame
: public blink::WebServiceWorkerNetworkProvider {
public:
explicit WebServiceWorkerNetworkProviderForFrame(
std::unique_ptr<ServiceWorkerNetworkProvider> provider)
: provider_(std::move(provider)) {}
void WillSendRequest(blink::WebURLRequest& request) override {
if (!request.GetExtraData())
request.SetExtraData(std::make_unique<RequestExtraData>());
auto* extra_data = static_cast<RequestExtraData*>(request.GetExtraData());
extra_data->set_service_worker_provider_id(provider_->provider_id());
// If the provider does not have a controller at this point, the renderer
// expects the request to never be handled by a service worker, so call
// SetSkipServiceWorker() with true to skip service workers here. Otherwise,
// a service worker that is in the process of becoming the controller (i.e.,
// via claim()) on the browser-side could handle the request and break the
// assumptions of the renderer.
if (request.GetFrameType() !=
network::mojom::RequestContextFrameType::kTopLevel &&
request.GetFrameType() !=
network::mojom::RequestContextFrameType::kNested &&
provider_->IsControlledByServiceWorker() ==
blink::mojom::ControllerServiceWorkerMode::kNoController) {
request.SetSkipServiceWorker(true);
}
}
int ProviderID() const override { return provider_->provider_id(); }
blink::mojom::ControllerServiceWorkerMode IsControlledByServiceWorker()
override {
return provider_->IsControlledByServiceWorker();
}
int64_t ControllerServiceWorkerID() override {
if (provider_->context())
return provider_->context()->GetControllerVersionId();
return blink::mojom::kInvalidServiceWorkerVersionId;
}
ServiceWorkerNetworkProvider* provider() { return provider_.get(); }
std::unique_ptr<blink::WebURLLoader> CreateURLLoader(
const blink::WebURLRequest& request,
std::unique_ptr<blink::scheduler::WebResourceLoadingTaskRunnerHandle>
task_runner_handle) override {
// RenderThreadImpl is nullptr in some tests.
if (!RenderThreadImpl::current())
return nullptr;
// S13nServiceWorker:
// We only install our own URLLoader if Servicification is enabled.
if (!blink::ServiceWorkerUtils::IsServicificationEnabled())
return nullptr;
// We need SubresourceLoaderFactory populated in order to create our own
// URLLoader for subresource loading.
if (!provider_->context() ||
!provider_->context()->GetSubresourceLoaderFactory())
return nullptr;
// If the URL is not http(s) or otherwise whitelisted, do not intercept the
// request. Schemes like 'blob' and 'file' are not eligible to be
// intercepted by service workers.
// TODO(falken): Let ServiceWorkerSubresourceLoaderFactory handle the
// request and move this check there (i.e., for such URLs, it should use
// its fallback factory).
const GURL gurl(request.Url());
if (!gurl.SchemeIsHTTPOrHTTPS() && !OriginCanAccessServiceWorkers(gurl))
return nullptr;
// If GetSkipServiceWorker() returns true, do not intercept the request.
if (request.GetSkipServiceWorker())
return nullptr;
// Create our own SubresourceLoader to route the request to the controller
// ServiceWorker.
// TODO(crbug.com/796425): Temporarily wrap the raw mojom::URLLoaderFactory
// pointer into SharedURLLoaderFactory.
return std::make_unique<WebURLLoaderImpl>(
RenderThreadImpl::current()->resource_dispatcher(),
std::move(task_runner_handle),
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
provider_->context()->GetSubresourceLoaderFactory()));
}
void DispatchNetworkQuiet() override { provider_->DispatchNetworkQuiet(); }
private:
std::unique_ptr<ServiceWorkerNetworkProvider> provider_;
};
} // namespace
// static
std::unique_ptr<blink::WebServiceWorkerNetworkProvider>
ServiceWorkerNetworkProvider::CreateForNavigation(
int route_id,
const RequestNavigationParams* request_params,
blink::WebLocalFrame* frame,
mojom::ControllerServiceWorkerInfoPtr controller_info,
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
// Determine if a ServiceWorkerNetworkProvider should be created and properly
// initialized for the navigation. A default ServiceWorkerNetworkProvider
// will always be created since it is expected in a certain number of places,
// however it will have an invalid id.
bool should_create_provider = false;
int provider_id = kInvalidServiceWorkerProviderId;
if (request_params) {
should_create_provider = request_params->should_create_service_worker;
provider_id = request_params->service_worker_provider_id;
} else {
should_create_provider =
((frame->EffectiveSandboxFlags() & blink::WebSandboxFlags::kOrigin) !=
blink::WebSandboxFlags::kOrigin);
}
// If we shouldn't create a real ServiceWorkerNetworkProvider, return one with
// an invalid id.
if (!should_create_provider) {
return std::make_unique<WebServiceWorkerNetworkProviderForFrame>(
base::WrapUnique(new ServiceWorkerNetworkProvider()));
}
// Otherwise, create the ServiceWorkerNetworkProvider.
// Ideally Document::IsSecureContext would be called here, but the document is
// not created yet, and due to redirects the URL may change. So pass
// is_parent_frame_secure to the browser process, so it can determine the
// context security when deciding whether to allow a service worker to control
// the document.
const bool is_parent_frame_secure = IsFrameSecure(frame->Parent());
// If the browser process did not assign a provider id already, assign one
// now (see class comments for content::ServiceWorkerProviderHost).
DCHECK(ServiceWorkerUtils::IsBrowserAssignedProviderId(provider_id) ||
provider_id == kInvalidServiceWorkerProviderId);
if (provider_id == kInvalidServiceWorkerProviderId)
provider_id = GetNextProviderId();
auto provider = base::WrapUnique(new ServiceWorkerNetworkProvider(
route_id, blink::mojom::ServiceWorkerProviderType::kForWindow,
provider_id, is_parent_frame_secure, std::move(controller_info),
std::move(fallback_loader_factory)));
return std::make_unique<WebServiceWorkerNetworkProviderForFrame>(
std::move(provider));
}
// static
std::unique_ptr<ServiceWorkerNetworkProvider>
ServiceWorkerNetworkProvider::CreateForSharedWorker(
mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
network::mojom::URLLoaderFactoryAssociatedPtrInfo
script_loader_factory_info,
mojom::ControllerServiceWorkerInfoPtr controller_info,
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
// S13nServiceWorker: |info| holds info about the precreated provider host.
if (info) {
DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
return base::WrapUnique(new ServiceWorkerNetworkProvider(
std::move(info), std::move(script_loader_factory_info),
std::move(controller_info), std::move(fallback_loader_factory)));
}
return base::WrapUnique(new ServiceWorkerNetworkProvider(
MSG_ROUTING_NONE,
blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
GetNextProviderId(), true /* is_parent_frame_secure */,
nullptr /* controller_service_worker */,
std::move(fallback_loader_factory)));
}
// static
std::unique_ptr<ServiceWorkerNetworkProvider>
ServiceWorkerNetworkProvider::CreateForController(
mojom::ServiceWorkerProviderInfoForStartWorkerPtr info) {
return base::WrapUnique(new ServiceWorkerNetworkProvider(std::move(info)));
}
// static
ServiceWorkerNetworkProvider*
ServiceWorkerNetworkProvider::FromWebServiceWorkerNetworkProvider(
blink::WebServiceWorkerNetworkProvider* provider) {
if (!provider) {
DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled());
return nullptr;
}
return static_cast<WebServiceWorkerNetworkProviderForFrame*>(provider)
->provider();
}
ServiceWorkerNetworkProvider::~ServiceWorkerNetworkProvider() {
if (context()) {
context()->OnNetworkProviderDestroyed();
}
}
int ServiceWorkerNetworkProvider::provider_id() const {
if (!context())
return kInvalidServiceWorkerProviderId;
return context()->provider_id();
}
blink::mojom::ControllerServiceWorkerMode
ServiceWorkerNetworkProvider::IsControlledByServiceWorker() const {
if (!context())
return blink::mojom::ControllerServiceWorkerMode::kNoController;
return context()->IsControlledByServiceWorker();
}
void ServiceWorkerNetworkProvider::DispatchNetworkQuiet() {
if (!context())
return;
context()->DispatchNetworkQuiet();
}
// Creates an invalid instance (provider_id() returns
// kInvalidServiceWorkerProviderId).
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider() {}
// Constructor for service worker clients.
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
int route_id,
blink::mojom::ServiceWorkerProviderType provider_type,
int provider_id,
bool is_parent_frame_secure,
mojom::ControllerServiceWorkerInfoPtr controller_info,
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
DCHECK_NE(provider_id, kInvalidServiceWorkerProviderId);
DCHECK(provider_type == blink::mojom::ServiceWorkerProviderType::kForWindow ||
provider_type ==
blink::mojom::ServiceWorkerProviderType::kForSharedWorker);
auto host_info = mojom::ServiceWorkerProviderHostInfo::New(
provider_id, route_id, provider_type, is_parent_frame_secure,
nullptr /* host_request */, nullptr /* client_ptr_info */);
mojom::ServiceWorkerContainerAssociatedRequest client_request =
mojo::MakeRequest(&host_info->client_ptr_info);
mojom::ServiceWorkerContainerHostAssociatedPtrInfo host_ptr_info;
host_info->host_request = mojo::MakeRequest(&host_ptr_info);
DCHECK(host_info->host_request.is_pending());
DCHECK(host_info->host_request.handle().is_valid());
// current() may be null in tests.
if (ChildThreadImpl::current()) {
context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
provider_id, provider_type, std::move(client_request),
std::move(host_ptr_info), std::move(controller_info),
std::move(fallback_loader_factory));
ChildThreadImpl::current()->channel()->GetRemoteAssociatedInterface(
&dispatcher_host_);
dispatcher_host_->OnProviderCreated(std::move(host_info));
} else {
context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
provider_id, provider_type, std::move(client_request),
std::move(host_ptr_info), std::move(controller_info),
std::move(fallback_loader_factory));
}
}
// Constructor for precreated shared worker.
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
mojom::ServiceWorkerProviderInfoForSharedWorkerPtr info,
network::mojom::URLLoaderFactoryAssociatedPtrInfo
script_loader_factory_info,
mojom::ControllerServiceWorkerInfoPtr controller_info,
scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory) {
context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
info->provider_id,
blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
std::move(info->client_request), std::move(info->host_ptr_info),
std::move(controller_info), std::move(fallback_loader_factory));
if (script_loader_factory_info.is_valid())
script_loader_factory_.Bind(std::move(script_loader_factory_info));
}
// Constructor for service worker execution contexts.
ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
mojom::ServiceWorkerProviderInfoForStartWorkerPtr info) {
context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
info->provider_id, std::move(info->client_request),
std::move(info->host_ptr_info));
if (info->script_loader_factory_ptr_info.is_valid()) {
script_loader_factory_.Bind(
std::move(info->script_loader_factory_ptr_info));
}
}
} // namespace content