blob: 8c21ad6e5be6ebba28a0d1723ed1f3c6ec912861 [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/browser/service_worker/service_worker_context_request_handler.h"
#include "base/command_line.h"
#include "base/time/time.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/browser/service_worker/service_worker_read_from_cache_job.h"
#include "content/browser/service_worker/service_worker_storage.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/service_worker/service_worker_write_to_cache_job.h"
#include "content/public/common/content_switches.h"
#include "net/base/load_flags.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_with_source.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_error_job.h"
#include "services/network/public/cpp/resource_response_info.h"
namespace content {
namespace {
bool ShouldBypassCacheDueToUpdateViaCache(
bool is_main_script,
blink::mojom::ServiceWorkerUpdateViaCache cache_mode) {
// TODO(https://crbug.com/675540): Remove the command line check and always
// respect cache_mode when shipping updateViaCache flag to stable.
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures)) {
return false;
}
switch (cache_mode) {
case blink::mojom::ServiceWorkerUpdateViaCache::kImports:
return is_main_script;
case blink::mojom::ServiceWorkerUpdateViaCache::kNone:
return true;
case blink::mojom::ServiceWorkerUpdateViaCache::kAll:
return false;
}
NOTREACHED() << static_cast<int>(cache_mode);
return false;
}
} // namespace
ServiceWorkerContextRequestHandler::ServiceWorkerContextRequestHandler(
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),
version_(provider_host_->running_hosted_version()) {
DCHECK(provider_host_->IsHostToRunningServiceWorker());
}
ServiceWorkerContextRequestHandler::~ServiceWorkerContextRequestHandler() {
}
// static
std::string ServiceWorkerContextRequestHandler::CreateJobStatusToString(
CreateJobStatus status) {
switch (status) {
case CreateJobStatus::UNINITIALIZED:
return "UNINITIALIZED";
case CreateJobStatus::WRITE_JOB:
return "WRITE_JOB";
case CreateJobStatus::WRITE_JOB_WITH_INCUMBENT:
return "WRITE_JOB_WITH_INCUMBENT";
case CreateJobStatus::READ_JOB:
return "READ_JOB";
case CreateJobStatus::READ_JOB_FOR_DUPLICATE_SCRIPT_IMPORT:
return "READ_JOB_FOR_DUPLICATE_SCRIPT_IMPORT";
case CreateJobStatus::ERROR_NO_PROVIDER:
return "ERROR_NO_PROVIDER";
case CreateJobStatus::ERROR_REDUNDANT_VERSION:
return "ERROR_REDUNDANT_VERSION";
case CreateJobStatus::ERROR_NO_CONTEXT:
return "ERROR_NO_CONTEXT";
case CreateJobStatus::ERROR_REDIRECT:
return "ERROR_REDIRECT";
case CreateJobStatus::ERROR_UNINSTALLED_SCRIPT_IMPORT:
return "ERROR_UNINSTALLED_SCRIPT_IMPORT";
case CreateJobStatus::ERROR_OUT_OF_RESOURCE_IDS:
return "ERROR_OUT_OF_RESOURCE_IDS";
case CreateJobStatus::NUM_TYPES:
NOTREACHED();
}
NOTREACHED() << static_cast<int>(status);
return "UNKNOWN";
}
net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJob(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
ResourceContext* resource_context) {
// We only use the script cache for main script loading and
// importScripts(), even if a cached script is xhr'd, we don't
// retrieve it from the script cache.
// TODO(falken): Get the desired behavior clarified in the spec,
// and make tweak the behavior here to match.
if (resource_type_ != RESOURCE_TYPE_SERVICE_WORKER &&
resource_type_ != RESOURCE_TYPE_SCRIPT) {
// Fall back to network.
return nullptr;
}
CreateJobStatus status = CreateJobStatus::UNINITIALIZED;
net::URLRequestJob* job =
MaybeCreateJobImpl(request, network_delegate, &status);
const bool is_main_script = resource_type_ == RESOURCE_TYPE_SERVICE_WORKER;
ServiceWorkerMetrics::RecordContextRequestHandlerStatus(
status, ServiceWorkerVersion::IsInstalled(version_->status()),
is_main_script);
if (job)
return job;
// If we got here, a job couldn't be created. Return an error job rather than
// falling back to network. Otherwise the renderer may receive the response
// from network and start a service worker whose browser-side
// ServiceWorkerVersion is not properly initialized.
//
// As an exception, allow installed service workers to use importScripts()
// to import non-installed scripts.
// TODO(falken): This is a spec violation that should be deprecated and
// removed. See https://github.com/w3c/ServiceWorker/issues/1021
if (status == CreateJobStatus::ERROR_UNINSTALLED_SCRIPT_IMPORT) {
// Fall back to network.
ServiceWorkerMetrics::RecordUninstalledScriptImport(version_->script_url());
return nullptr;
}
std::string error_str(CreateJobStatusToString(status));
request->net_log().AddEvent(
net::NetLogEventType::SERVICE_WORKER_SCRIPT_LOAD_UNHANDLED_REQUEST_ERROR,
net::NetLog::StringCallback("error", &error_str));
return new net::URLRequestErrorJob(request, network_delegate,
net::Error::ERR_FAILED);
}
net::URLRequestJob* ServiceWorkerContextRequestHandler::MaybeCreateJobImpl(
net::URLRequest* request,
net::NetworkDelegate* network_delegate,
CreateJobStatus* out_status) {
if (!context_) {
*out_status = CreateJobStatus::ERROR_NO_CONTEXT;
return nullptr;
}
if (!provider_host_) {
*out_status = CreateJobStatus::ERROR_NO_PROVIDER;
return nullptr;
}
// 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.
if (version_->status() == ServiceWorkerVersion::REDUNDANT) {
*out_status = CreateJobStatus::ERROR_REDUNDANT_VERSION;
return nullptr;
}
// We currently have no use case for hijacking a redirected request.
if (request->url_chain().size() > 1) {
*out_status = CreateJobStatus::ERROR_REDIRECT;
return nullptr;
}
const bool is_main_script = resource_type_ == RESOURCE_TYPE_SERVICE_WORKER;
int64_t resource_id =
version_->script_cache_map()->LookupResourceId(request->url());
if (resource_id != kInvalidServiceWorkerResourceId) {
if (ServiceWorkerVersion::IsInstalled(version_->status())) {
// An installed worker is loading a stored script.
if (is_main_script)
version_->embedded_worker()->OnURLJobCreatedForMainScript();
*out_status = CreateJobStatus::READ_JOB;
} else {
// A new worker is loading a stored script. The script was already
// imported (or the main script is being recursively imported).
*out_status = CreateJobStatus::READ_JOB_FOR_DUPLICATE_SCRIPT_IMPORT;
}
return new ServiceWorkerReadFromCacheJob(request, network_delegate,
resource_type_, context_, version_,
resource_id);
}
// An installed worker is importing a non-stored script.
if (ServiceWorkerVersion::IsInstalled(version_->status())) {
DCHECK(!is_main_script);
*out_status = CreateJobStatus::ERROR_UNINSTALLED_SCRIPT_IMPORT;
return nullptr;
}
// A new worker is loading a script for the first time. Create a write job to
// store the script.
ServiceWorkerRegistration* registration =
context_->GetLiveRegistration(version_->registration_id());
DCHECK(registration); // We're registering or updating so must be there.
resource_id = context_->storage()->NewResourceId();
if (resource_id == kInvalidServiceWorkerResourceId) {
*out_status = CreateJobStatus::ERROR_OUT_OF_RESOURCE_IDS;
return nullptr;
}
// Bypass the browser cache for initial installs and update checks after 24
// hours have passed.
int extra_load_flags = 0;
base::TimeDelta time_since_last_check =
base::Time::Now() - registration->last_update_check();
if (ShouldBypassCacheDueToUpdateViaCache(is_main_script,
registration->update_via_cache()) ||
time_since_last_check > kServiceWorkerScriptMaxCacheAge ||
version_->force_bypass_cache_for_scripts()) {
extra_load_flags = net::LOAD_BYPASS_CACHE;
}
ServiceWorkerVersion* stored_version = registration->waiting_version()
? registration->waiting_version()
: registration->active_version();
int64_t incumbent_resource_id = kInvalidServiceWorkerResourceId;
if (is_main_script) {
if (stored_version && stored_version->script_url() == request->url()) {
incumbent_resource_id =
stored_version->script_cache_map()->LookupResourceId(request->url());
}
version_->embedded_worker()->OnURLJobCreatedForMainScript();
}
*out_status = incumbent_resource_id == kInvalidServiceWorkerResourceId
? CreateJobStatus::WRITE_JOB
: CreateJobStatus::WRITE_JOB_WITH_INCUMBENT;
return new ServiceWorkerWriteToCacheJob(
request, network_delegate, resource_type_, context_, version_.get(),
extra_load_flags, resource_id, incumbent_resource_id);
}
} // namespace content