blob: 9eb50bcd0930e402b95ef0cc1f9e6a9c53ffd6a6 [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_registration_object_host.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "content/browser/service_worker/service_worker_consts.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_object_host.h"
#include "content/browser/service_worker/service_worker_provider_host.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/http/http_util.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
namespace content {
namespace {
constexpr base::TimeDelta kSelfUpdateDelay = base::TimeDelta::FromSeconds(30);
constexpr base::TimeDelta kMaxSelfUpdateDelay = base::TimeDelta::FromMinutes(3);
// Returns an object info to send over Mojo. The info must be sent immediately.
// See ServiceWorkerObjectHost::CreateCompleteObjectInfoToSend() for details.
blink::mojom::ServiceWorkerObjectInfoPtr CreateCompleteObjectInfoToSend(
ServiceWorkerProviderHost* provider_host,
ServiceWorkerVersion* version) {
base::WeakPtr<ServiceWorkerObjectHost> service_worker_object_host =
provider_host->GetOrCreateServiceWorkerObjectHost(version);
if (!service_worker_object_host)
return nullptr;
return service_worker_object_host->CreateCompleteObjectInfoToSend();
}
void ExecuteUpdate(base::WeakPtr<ServiceWorkerContextCore> context,
int64_t registration_id,
bool force_bypass_cache,
bool skip_script_comparison,
ServiceWorkerContextCore::UpdateCallback callback,
blink::ServiceWorkerStatusCode status) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (status != blink::ServiceWorkerStatusCode::kOk) {
// The delay was already very long and update() is rejected immediately.
DCHECK_EQ(blink::ServiceWorkerStatusCode::kErrorTimeout, status);
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorTimeout,
ServiceWorkerConsts::kUpdateTimeoutErrorMesage,
registration_id);
return;
}
if (!context) {
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorAbort,
ServiceWorkerConsts::kShutdownErrorMessage,
registration_id);
return;
}
ServiceWorkerRegistration* registration =
context->GetLiveRegistration(registration_id);
if (!registration) {
// The service worker is no longer running, so update() won't be rejected.
// We still run the callback so the caller knows.
std::move(callback).Run(blink::ServiceWorkerStatusCode::kErrorTimeout,
ServiceWorkerConsts::kUpdateTimeoutErrorMesage,
registration_id);
return;
}
context->UpdateServiceWorker(registration, force_bypass_cache,
skip_script_comparison, std::move(callback));
}
} // anonymous namespace
ServiceWorkerRegistrationObjectHost::ServiceWorkerRegistrationObjectHost(
base::WeakPtr<ServiceWorkerContextCore> context,
ServiceWorkerProviderHost* provider_host,
scoped_refptr<ServiceWorkerRegistration> registration)
: provider_host_(provider_host),
context_(context),
registration_(registration),
weak_ptr_factory_(this) {
DCHECK(registration_.get());
DCHECK(provider_host_);
registration_->AddListener(this);
bindings_.set_connection_error_handler(
base::Bind(&ServiceWorkerRegistrationObjectHost::OnConnectionError,
base::Unretained(this)));
}
ServiceWorkerRegistrationObjectHost::~ServiceWorkerRegistrationObjectHost() {
DCHECK(registration_.get());
registration_->RemoveListener(this);
}
blink::mojom::ServiceWorkerRegistrationObjectInfoPtr
ServiceWorkerRegistrationObjectHost::CreateObjectInfo() {
// |info->options->script_type| is never accessed anywhere, so just set it to
// kClassic.
// TODO(asamidoi, nhiroki): Remove |options| from
// ServiceWorkerRegistrationObjectInfo, since |script_type| is a
// non-per-registration property.
blink::mojom::ScriptType script_type = blink::mojom::ScriptType::kClassic;
auto info = blink::mojom::ServiceWorkerRegistrationObjectInfo::New();
info->options = blink::mojom::ServiceWorkerRegistrationOptions::New(
registration_->scope(), script_type, registration_->update_via_cache());
info->registration_id = registration_->id();
bindings_.AddBinding(this, mojo::MakeRequest(&info->host_ptr_info));
info->request = mojo::MakeRequest(&remote_registration_);
info->installing = CreateCompleteObjectInfoToSend(
provider_host_, registration_->installing_version());
info->waiting = CreateCompleteObjectInfoToSend(
provider_host_, registration_->waiting_version());
info->active = CreateCompleteObjectInfoToSend(
provider_host_, registration_->active_version());
return info;
}
void ServiceWorkerRegistrationObjectHost::OnVersionAttributesChanged(
ServiceWorkerRegistration* registration,
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask,
const ServiceWorkerRegistrationInfo& info) {
DCHECK_EQ(registration->id(), registration_->id());
SetServiceWorkerObjects(
std::move(changed_mask), registration->installing_version(),
registration->waiting_version(), registration->active_version());
}
void ServiceWorkerRegistrationObjectHost::OnUpdateViaCacheChanged(
ServiceWorkerRegistration* registration) {
remote_registration_->SetUpdateViaCache(registration->update_via_cache());
}
void ServiceWorkerRegistrationObjectHost::OnRegistrationFailed(
ServiceWorkerRegistration* registration) {
DCHECK_EQ(registration->id(), registration_->id());
auto changed_mask =
blink::mojom::ChangedServiceWorkerObjectsMask::New(true, true, true);
SetServiceWorkerObjects(std::move(changed_mask), nullptr, nullptr, nullptr);
}
void ServiceWorkerRegistrationObjectHost::OnUpdateFound(
ServiceWorkerRegistration* registration) {
DCHECK(remote_registration_);
remote_registration_->UpdateFound();
}
void ServiceWorkerRegistrationObjectHost::Update(UpdateCallback callback) {
if (!CanServeRegistrationObjectHostMethods(&callback,
kServiceWorkerUpdateErrorPrefix)) {
return;
}
if (!registration_->GetNewestVersion()) {
// This can happen if update() is called during initial script evaluation.
// Abort the following steps according to the spec.
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kState,
std::string(kServiceWorkerUpdateErrorPrefix) +
std::string(ServiceWorkerConsts::kInvalidStateErrorMessage));
return;
}
DelayUpdate(
provider_host_->provider_type(), registration_.get(),
provider_host_->running_hosted_version(),
base::BindOnce(
&ExecuteUpdate, context_, registration_->id(),
false /* force_bypass_cache */, false /* skip_script_comparison */,
base::BindOnce(&ServiceWorkerRegistrationObjectHost::UpdateComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void ServiceWorkerRegistrationObjectHost::DelayUpdate(
blink::mojom::ServiceWorkerProviderType provider_type,
ServiceWorkerRegistration* registration,
ServiceWorkerVersion* version,
StatusCallback update_function) {
DCHECK(registration);
if (provider_type !=
blink::mojom::ServiceWorkerProviderType::kForServiceWorker ||
(version && version->HasControllee())) {
// Don't delay update() if called by non-workers or by workers with
// controllees.
std::move(update_function).Run(blink::ServiceWorkerStatusCode::kOk);
return;
}
base::TimeDelta delay = registration->self_update_delay();
if (delay > kMaxSelfUpdateDelay) {
std::move(update_function)
.Run(blink::ServiceWorkerStatusCode::kErrorTimeout);
return;
}
if (delay < kSelfUpdateDelay) {
registration->set_self_update_delay(kSelfUpdateDelay);
} else {
registration->set_self_update_delay(delay * 2);
}
if (delay < base::TimeDelta::Min()) {
// Only enforce the delay of update() iff |delay| exists.
std::move(update_function).Run(blink::ServiceWorkerStatusCode::kOk);
return;
}
base::PostDelayedTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(std::move(update_function),
blink::ServiceWorkerStatusCode::kOk),
delay);
}
void ServiceWorkerRegistrationObjectHost::Unregister(
UnregisterCallback callback) {
if (!CanServeRegistrationObjectHostMethods(
&callback, kServiceWorkerUnregisterErrorPrefix)) {
return;
}
context_->UnregisterServiceWorker(
registration_->scope(),
base::AdaptCallbackForRepeating(base::BindOnce(
&ServiceWorkerRegistrationObjectHost::UnregistrationComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
}
void ServiceWorkerRegistrationObjectHost::EnableNavigationPreload(
bool enable,
EnableNavigationPreloadCallback callback) {
if (!CanServeRegistrationObjectHostMethods(
&callback,
ServiceWorkerConsts::kEnableNavigationPreloadErrorPrefix)) {
return;
}
if (!registration_->active_version()) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kState,
std::string(ServiceWorkerConsts::kEnableNavigationPreloadErrorPrefix) +
std::string(ServiceWorkerConsts::kNoActiveWorkerErrorMessage));
return;
}
context_->storage()->UpdateNavigationPreloadEnabled(
registration_->id(), registration_->scope().GetOrigin(), enable,
base::AdaptCallbackForRepeating(base::BindOnce(
&ServiceWorkerRegistrationObjectHost::
DidUpdateNavigationPreloadEnabled,
weak_ptr_factory_.GetWeakPtr(), enable, std::move(callback))));
}
void ServiceWorkerRegistrationObjectHost::GetNavigationPreloadState(
GetNavigationPreloadStateCallback callback) {
if (!CanServeRegistrationObjectHostMethods(
&callback, ServiceWorkerConsts::kGetNavigationPreloadStateErrorPrefix,
nullptr)) {
return;
}
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt,
registration_->navigation_preload_state().Clone());
}
void ServiceWorkerRegistrationObjectHost::SetNavigationPreloadHeader(
const std::string& value,
SetNavigationPreloadHeaderCallback callback) {
if (!CanServeRegistrationObjectHostMethods(
&callback,
ServiceWorkerConsts::kSetNavigationPreloadHeaderErrorPrefix)) {
return;
}
if (!registration_->active_version()) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kState,
std::string(
ServiceWorkerConsts::kSetNavigationPreloadHeaderErrorPrefix) +
std::string(ServiceWorkerConsts::kNoActiveWorkerErrorMessage));
return;
}
// TODO(falken): Ideally this would match Blink's isValidHTTPHeaderValue.
// Chrome's check is less restrictive: it allows non-latin1 characters.
if (!net::HttpUtil::IsValidHeaderValue(value)) {
bindings_.ReportBadMessage(
ServiceWorkerConsts::kBadNavigationPreloadHeaderValue);
return;
}
context_->storage()->UpdateNavigationPreloadHeader(
registration_->id(), registration_->scope().GetOrigin(), value,
base::AdaptCallbackForRepeating(base::BindOnce(
&ServiceWorkerRegistrationObjectHost::
DidUpdateNavigationPreloadHeader,
weak_ptr_factory_.GetWeakPtr(), value, std::move(callback))));
}
void ServiceWorkerRegistrationObjectHost::UpdateComplete(
UpdateCallback callback,
blink::ServiceWorkerStatusCode status,
const std::string& status_message,
int64_t registration_id) {
if (status != blink::ServiceWorkerStatusCode::kOk) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, status_message,
&error_type, &error_message);
std::move(callback).Run(error_type,
kServiceWorkerUpdateErrorPrefix + error_message);
return;
}
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt);
}
void ServiceWorkerRegistrationObjectHost::UnregistrationComplete(
UnregisterCallback callback,
blink::ServiceWorkerStatusCode status) {
if (status != blink::ServiceWorkerStatusCode::kOk) {
std::string error_message;
blink::mojom::ServiceWorkerErrorType error_type;
GetServiceWorkerErrorTypeForRegistration(status, std::string(), &error_type,
&error_message);
std::move(callback).Run(
error_type, kServiceWorkerUnregisterErrorPrefix + error_message);
return;
}
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt);
}
void ServiceWorkerRegistrationObjectHost::DidUpdateNavigationPreloadEnabled(
bool enable,
EnableNavigationPreloadCallback callback,
blink::ServiceWorkerStatusCode status) {
if (status != blink::ServiceWorkerStatusCode::kOk) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(ServiceWorkerConsts::kEnableNavigationPreloadErrorPrefix) +
std::string(ServiceWorkerConsts::kDatabaseErrorMessage));
return;
}
if (registration_)
registration_->EnableNavigationPreload(enable);
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt);
}
void ServiceWorkerRegistrationObjectHost::DidUpdateNavigationPreloadHeader(
const std::string& value,
SetNavigationPreloadHeaderCallback callback,
blink::ServiceWorkerStatusCode status) {
if (status != blink::ServiceWorkerStatusCode::kOk) {
std::move(callback).Run(
blink::mojom::ServiceWorkerErrorType::kUnknown,
std::string(
ServiceWorkerConsts::kSetNavigationPreloadHeaderErrorPrefix) +
std::string(ServiceWorkerConsts::kDatabaseErrorMessage));
return;
}
if (registration_)
registration_->SetNavigationPreloadHeader(value);
std::move(callback).Run(blink::mojom::ServiceWorkerErrorType::kNone,
base::nullopt);
}
void ServiceWorkerRegistrationObjectHost::SetServiceWorkerObjects(
blink::mojom::ChangedServiceWorkerObjectsMaskPtr changed_mask,
ServiceWorkerVersion* installing_version,
ServiceWorkerVersion* waiting_version,
ServiceWorkerVersion* active_version) {
if (!(changed_mask->installing || changed_mask->waiting ||
changed_mask->active))
return;
blink::mojom::ServiceWorkerObjectInfoPtr installing;
blink::mojom::ServiceWorkerObjectInfoPtr waiting;
blink::mojom::ServiceWorkerObjectInfoPtr active;
if (changed_mask->installing) {
installing =
CreateCompleteObjectInfoToSend(provider_host_, installing_version);
}
if (changed_mask->waiting)
waiting = CreateCompleteObjectInfoToSend(provider_host_, waiting_version);
if (changed_mask->active)
active = CreateCompleteObjectInfoToSend(provider_host_, active_version);
DCHECK(remote_registration_);
remote_registration_->SetServiceWorkerObjects(
std::move(changed_mask), std::move(installing), std::move(waiting),
std::move(active));
}
void ServiceWorkerRegistrationObjectHost::OnConnectionError() {
// If there are still bindings, |this| is still being used.
if (!bindings_.empty())
return;
// Will destroy |this|.
provider_host_->RemoveServiceWorkerRegistrationObjectHost(
registration()->id());
}
template <typename CallbackType, typename... Args>
bool ServiceWorkerRegistrationObjectHost::CanServeRegistrationObjectHostMethods(
CallbackType* callback,
const char* error_prefix,
Args... args) {
if (!context_) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kAbort,
std::string(error_prefix) +
std::string(ServiceWorkerConsts::kShutdownErrorMessage),
args...);
return false;
}
// TODO(falken): This check can be removed once crbug.com/439697 is fixed.
// (Also see crbug.com/776408)
if (provider_host_->document_url().is_empty()) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kSecurity,
std::string(error_prefix) +
std::string(ServiceWorkerConsts::kNoDocumentURLErrorMessage),
args...);
return false;
}
std::vector<GURL> urls = {provider_host_->document_url(),
registration_->scope()};
if (!ServiceWorkerUtils::AllOriginsMatchAndCanAccessServiceWorkers(urls)) {
bindings_.ReportBadMessage(ServiceWorkerConsts::kBadMessageImproperOrigins);
return false;
}
if (!provider_host_->AllowServiceWorker(registration_->scope())) {
std::move(*callback).Run(
blink::mojom::ServiceWorkerErrorType::kDisabled,
std::string(error_prefix) +
std::string(ServiceWorkerConsts::kUserDeniedPermissionMessage),
args...);
return false;
}
return true;
}
} // namespace content