blob: 6d779103cd521b3ea15dfde7ca09f87460be565e [file] [log] [blame]
// Copyright 2017 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_script_loader_factory.h"
#include <memory>
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_installed_script_loader.h"
#include "content/browser/service_worker/service_worker_new_script_loader.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "services/network/public/cpp/resource_response.h"
namespace content {
ServiceWorkerScriptLoaderFactory::ServiceWorkerScriptLoaderFactory(
base::WeakPtr<ServiceWorkerContextCore> context,
base::WeakPtr<ServiceWorkerProviderHost> provider_host,
scoped_refptr<URLLoaderFactoryGetter> loader_factory_getter,
network::mojom::URLLoaderFactoryPtr non_network_loader_factory)
: context_(context),
provider_host_(provider_host),
loader_factory_getter_(loader_factory_getter),
non_network_loader_factory_(std::move(non_network_loader_factory)) {
DCHECK(provider_host_->IsProviderForServiceWorker());
}
ServiceWorkerScriptLoaderFactory::~ServiceWorkerScriptLoaderFactory() = default;
void ServiceWorkerScriptLoaderFactory::CreateLoaderAndStart(
network::mojom::URLLoaderRequest request,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& resource_request,
network::mojom::URLLoaderClientPtr client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(ServiceWorkerUtils::IsServicificationEnabled());
DCHECK(loader_factory_getter_);
if (!ShouldHandleScriptRequest(resource_request)) {
// If the request should not be handled (e.g., a fetch() request), just do a
// passthrough load. This needs a relaying as we use different associated
// message pipes.
// TODO(kinuko): Record the reason like what we do with netlog in
// ServiceWorkerContextRequestHandler.
if (!resource_request.url.SchemeIsHTTPOrHTTPS() &&
non_network_loader_factory_) {
non_network_loader_factory_->CreateLoaderAndStart(
std::move(request), routing_id, request_id, options, resource_request,
std::move(client), traffic_annotation);
} else {
loader_factory_getter_->GetNetworkFactory()->CreateLoaderAndStart(
std::move(request), routing_id, request_id, options, resource_request,
std::move(client), traffic_annotation);
}
return;
}
// If we get here, the service worker is not installed, so the script is
// usually not yet installed. However, there is a special case when an
// installing worker that imports the same script twice (e.g.
// importScripts('dupe.js'); importScripts('dupe.js');) or if it recursively
// imports the main script. In this case, read the installed script from
// storage.
scoped_refptr<ServiceWorkerVersion> version =
provider_host_->running_hosted_version();
int64_t resource_id =
version->script_cache_map()->LookupResourceId(resource_request.url);
if (resource_id != kInvalidServiceWorkerResourceId) {
std::unique_ptr<ServiceWorkerResponseReader> response_reader =
context_->storage()->CreateResponseReader(resource_id);
mojo::MakeStrongBinding(
std::make_unique<ServiceWorkerInstalledScriptLoader>(
options, std::move(client), std::move(response_reader)),
std::move(request));
return;
}
// The common case: load the script and install it.
network::mojom::URLLoaderFactoryPtr cloned_non_network_loader_factory;
if (!resource_request.url.SchemeIsHTTPOrHTTPS() &&
non_network_loader_factory_) {
non_network_loader_factory_->Clone(
mojo::MakeRequest(&cloned_non_network_loader_factory));
}
mojo::MakeStrongBinding(
std::make_unique<ServiceWorkerNewScriptLoader>(
routing_id, request_id, options, resource_request, std::move(client),
provider_host_->running_hosted_version(), loader_factory_getter_,
std::move(cloned_non_network_loader_factory), traffic_annotation),
std::move(request));
}
void ServiceWorkerScriptLoaderFactory::Clone(
network::mojom::URLLoaderFactoryRequest request) {
// This method is required to support synchronous requests which are not
// performed during installation.
NOTREACHED();
}
bool ServiceWorkerScriptLoaderFactory::ShouldHandleScriptRequest(
const network::ResourceRequest& resource_request) {
if (!context_ || !provider_host_)
return false;
scoped_refptr<ServiceWorkerVersion> version =
provider_host_->running_hosted_version();
if (!version)
return false;
// Handle only the service worker main script (RESOURCE_TYPE_SERVICE_WORKER)
// or importScripts() (RESOURCE_TYPE_SCRIPT).
switch (resource_request.resource_type) {
case RESOURCE_TYPE_SERVICE_WORKER:
// The main script should be fetched only when we start a new service
// worker.
if (version->status() != ServiceWorkerVersion::NEW)
return false;
break;
case RESOURCE_TYPE_SCRIPT:
// TODO(nhiroki): In the current implementation, importScripts() can be
// called in any ServiceWorkerVersion::Status except for REDUNDANT, but
// the spec defines importScripts() works only on the initial script
// evaluation and the install event. Update this check once
// importScripts() is fixed (https://crbug.com/719052).
if (version->status() == ServiceWorkerVersion::REDUNDANT) {
// This could happen if browser-side has set the status to redundant but
// the worker has not yet stopped. The worker is already doomed so just
// reject the request. Handle it specially here because otherwise it'd
// be unclear whether "REDUNDANT" should count as installed or not
// installed when making decisions about how to handle the request and
// logging UMA.
return false;
}
break;
default:
// TODO(nhiroki): Record bad message, we shouldn't come here for other
// request types.
NOTREACHED();
return false;
}
// TODO(falken): Make sure we don't handle a redirected request.
// For installed service workers, typically all the scripts are served via
// script streaming, so we don't come here. However, we still come here when
// the service worker is importing a script that was never installed. For now,
// return false here to fallback to network. Eventually, it should be
// deprecated (https://crbug.com/719052).
if (ServiceWorkerVersion::IsInstalled(version->status()))
return false;
return true;
}
} // namespace content