blob: 3f9bd267a2199aa9017924c38f498eb933318455 [file] [log] [blame]
// Copyright 2013 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_dispatcher_host.h"
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/bad_message.h"
#include "content/browser/service_worker/embedded_worker_registry.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_client_utils.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_handle.h"
#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/common/service_worker/embedded_worker_messages.h"
#include "content/common/service_worker/service_worker_event_dispatcher.mojom.h"
#include "content/common/service_worker/service_worker_messages.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/content_client.h"
#include "content/public/common/origin_util.h"
#include "ipc/ipc_message_macros.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/WebServiceWorkerError.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/service_worker_error_type.mojom.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/service_worker_object.mojom.h"
#include "third_party/WebKit/public/platform/web_feature.mojom.h"
#include "url/gurl.h"
using blink::MessagePortChannel;
using blink::WebServiceWorkerError;
namespace content {
namespace {
const uint32_t kServiceWorkerFilteredMessageClasses[] = {
ServiceWorkerMsgStart, EmbeddedWorkerMsgStart,
};
} // namespace
ServiceWorkerDispatcherHost::ServiceWorkerDispatcherHost(
int render_process_id,
ResourceContext* resource_context)
: BrowserMessageFilter(kServiceWorkerFilteredMessageClasses,
arraysize(kServiceWorkerFilteredMessageClasses)),
BrowserAssociatedInterface<mojom::ServiceWorkerDispatcherHost>(this,
this),
render_process_id_(render_process_id),
resource_context_(resource_context),
channel_ready_(false),
weak_ptr_factory_(this) {}
ServiceWorkerDispatcherHost::~ServiceWorkerDispatcherHost() {
// Temporary CHECK for debugging https://crbug.com/736203.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (GetContext() && phase_ == Phase::kAddedToContext)
GetContext()->RemoveDispatcherHost(render_process_id_);
}
void ServiceWorkerDispatcherHost::Init(
ServiceWorkerContextWrapper* context_wrapper) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&ServiceWorkerDispatcherHost::Init, this,
base::RetainedRef(context_wrapper)));
return;
}
// Just speculating that maybe we were destructed before Init() was called on
// the IO thread in order to try to fix https://crbug.com/750267.
if (phase_ != Phase::kInitial)
return;
context_wrapper_ = context_wrapper;
if (!GetContext())
return;
GetContext()->AddDispatcherHost(render_process_id_, this);
phase_ = Phase::kAddedToContext;
}
void ServiceWorkerDispatcherHost::OnFilterAdded(IPC::Channel* channel) {
TRACE_EVENT0("ServiceWorker",
"ServiceWorkerDispatcherHost::OnFilterAdded");
// Temporary CHECK for debugging https://crbug.com/736203.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
channel_ready_ = true;
std::vector<std::unique_ptr<IPC::Message>> messages;
messages.swap(pending_messages_);
for (auto& message : messages) {
BrowserMessageFilter::Send(message.release());
}
}
void ServiceWorkerDispatcherHost::OnFilterRemoved() {
// Temporary CHECK for debugging https://crbug.com/736203.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Don't wait until the destructor to teardown since a new dispatcher host
// for this process might be created before then.
if (GetContext() && phase_ == Phase::kAddedToContext) {
GetContext()->RemoveDispatcherHost(render_process_id_);
weak_ptr_factory_.InvalidateWeakPtrs();
}
phase_ = Phase::kRemovedFromContext;
context_wrapper_ = nullptr;
channel_ready_ = false;
}
void ServiceWorkerDispatcherHost::OnDestruct() const {
// Destruct on the IO thread since |context_wrapper_| should only be accessed
// on the IO thread.
BrowserThread::DeleteOnIOThread::Destruct(this);
}
bool ServiceWorkerDispatcherHost::OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(ServiceWorkerDispatcherHost, message)
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_PostMessageToWorker,
OnPostMessageToWorker)
IPC_MESSAGE_HANDLER(EmbeddedWorkerHostMsg_CountFeature, OnCountFeature)
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_IncrementServiceWorkerRefCount,
OnIncrementServiceWorkerRefCount)
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_DecrementServiceWorkerRefCount,
OnDecrementServiceWorkerRefCount)
IPC_MESSAGE_HANDLER(ServiceWorkerHostMsg_TerminateWorker, OnTerminateWorker)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
if (!handled && GetContext()) {
handled = GetContext()->embedded_worker_registry()->OnMessageReceived(
message, render_process_id_);
if (!handled)
bad_message::ReceivedBadMessage(this, bad_message::SWDH_NOT_HANDLED);
}
return handled;
}
bool ServiceWorkerDispatcherHost::Send(IPC::Message* message) {
if (channel_ready_) {
BrowserMessageFilter::Send(message);
// Don't bother passing through Send()'s result: it's not reliable.
return true;
}
pending_messages_.push_back(base::WrapUnique(message));
return true;
}
void ServiceWorkerDispatcherHost::RegisterServiceWorkerHandle(
std::unique_ptr<ServiceWorkerHandle> handle) {
int handle_id = handle->handle_id();
handles_.AddWithID(std::move(handle), handle_id);
}
ServiceWorkerHandle* ServiceWorkerDispatcherHost::FindServiceWorkerHandle(
int provider_id,
int64_t version_id) {
for (base::IDMap<std::unique_ptr<ServiceWorkerHandle>>::iterator iter(
&handles_);
!iter.IsAtEnd(); iter.Advance()) {
ServiceWorkerHandle* handle = iter.GetCurrentValue();
DCHECK(handle);
DCHECK(handle->version());
if (handle->provider_id() == provider_id &&
handle->version()->version_id() == version_id) {
return handle;
}
}
return nullptr;
}
base::WeakPtr<ServiceWorkerDispatcherHost>
ServiceWorkerDispatcherHost::AsWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void ServiceWorkerDispatcherHost::OnPostMessageToWorker(
int handle_id,
int provider_id,
const base::string16& message,
const url::Origin& source_origin,
const std::vector<MessagePortChannel>& sent_message_ports) {
TRACE_EVENT0("ServiceWorker",
"ServiceWorkerDispatcherHost::OnPostMessageToWorker");
if (!GetContext())
return;
ServiceWorkerHandle* handle = handles_.Lookup(handle_id);
if (!handle) {
bad_message::ReceivedBadMessage(this, bad_message::SWDH_POST_MESSAGE);
return;
}
ServiceWorkerProviderHost* sender_provider_host =
GetContext()->GetProviderHost(render_process_id_, provider_id);
if (!sender_provider_host) {
// This may occur when destruction of the sender provider overtakes
// postMessage() because of thread hopping on WebServiceWorkerImpl.
return;
}
DispatchExtendableMessageEvent(
base::WrapRefCounted(handle->version()), message, source_origin,
sent_message_ports, sender_provider_host,
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
}
void ServiceWorkerDispatcherHost::DispatchExtendableMessageEvent(
scoped_refptr<ServiceWorkerVersion> worker,
const base::string16& message,
const url::Origin& source_origin,
const std::vector<MessagePortChannel>& sent_message_ports,
ServiceWorkerProviderHost* sender_provider_host,
const StatusCallback& callback) {
switch (sender_provider_host->provider_type()) {
case SERVICE_WORKER_PROVIDER_FOR_WINDOW:
case SERVICE_WORKER_PROVIDER_FOR_WORKER:
case SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER:
service_worker_client_utils::GetClient(
sender_provider_host,
base::Bind(&ServiceWorkerDispatcherHost::
DispatchExtendableMessageEventInternal<
ServiceWorkerClientInfo>,
this, worker, message, source_origin, sent_message_ports,
base::nullopt, callback));
break;
case SERVICE_WORKER_PROVIDER_FOR_CONTROLLER: {
// Clamp timeout to the sending worker's remaining timeout, to prevent
// postMessage from keeping workers alive forever.
base::TimeDelta timeout =
sender_provider_host->running_hosted_version()->remaining_timeout();
blink::mojom::ServiceWorkerObjectInfoPtr worker_info =
sender_provider_host->GetOrCreateServiceWorkerHandle(
sender_provider_host->running_hosted_version());
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&ServiceWorkerDispatcherHost::
DispatchExtendableMessageEventInternal<
blink::mojom::ServiceWorkerObjectInfo>,
this, worker, message, source_origin,
sent_message_ports, base::make_optional(timeout),
callback, *worker_info));
break;
}
case SERVICE_WORKER_PROVIDER_UNKNOWN:
default:
NOTREACHED() << sender_provider_host->provider_type();
break;
}
}
void ServiceWorkerDispatcherHost::OnProviderCreated(
ServiceWorkerProviderHostInfo info) {
TRACE_EVENT0("ServiceWorker",
"ServiceWorkerDispatcherHost::OnProviderCreated");
if (!GetContext())
return;
if (GetContext()->GetProviderHost(render_process_id_, info.provider_id)) {
bad_message::ReceivedBadMessage(
this, bad_message::SWDH_PROVIDER_CREATED_DUPLICATE_ID);
return;
}
if (IsBrowserSideNavigationEnabled() &&
ServiceWorkerUtils::IsBrowserAssignedProviderId(info.provider_id)) {
std::unique_ptr<ServiceWorkerProviderHost> provider_host;
// PlzNavigate
// Retrieve the provider host previously created for navigation requests.
ServiceWorkerNavigationHandleCore* navigation_handle_core =
GetContext()->GetNavigationHandleCore(info.provider_id);
if (navigation_handle_core != nullptr)
provider_host = navigation_handle_core->RetrievePreCreatedHost();
// If no host is found, create one.
if (provider_host == nullptr) {
GetContext()->AddProviderHost(ServiceWorkerProviderHost::Create(
render_process_id_, std::move(info), GetContext()->AsWeakPtr(),
AsWeakPtr()));
return;
}
// Otherwise, completed the initialization of the pre-created host.
if (info.type != SERVICE_WORKER_PROVIDER_FOR_WINDOW) {
bad_message::ReceivedBadMessage(
this, bad_message::SWDH_PROVIDER_CREATED_ILLEGAL_TYPE_NOT_WINDOW);
return;
}
provider_host->CompleteNavigationInitialized(render_process_id_,
std::move(info), AsWeakPtr());
GetContext()->AddProviderHost(std::move(provider_host));
} else {
// Provider host for controller should be pre-created on StartWorker in
// ServiceWorkerVersion.
if (info.type == SERVICE_WORKER_PROVIDER_FOR_CONTROLLER) {
bad_message::ReceivedBadMessage(
this, bad_message::SWDH_PROVIDER_CREATED_ILLEGAL_TYPE_CONTROLLER);
return;
}
if (ServiceWorkerUtils::IsBrowserAssignedProviderId(info.provider_id)) {
bad_message::ReceivedBadMessage(
this, bad_message::SWDH_PROVIDER_CREATED_BAD_ID);
return;
}
GetContext()->AddProviderHost(ServiceWorkerProviderHost::Create(
render_process_id_, std::move(info), GetContext()->AsWeakPtr(),
AsWeakPtr()));
}
}
template <typename SourceInfo>
void ServiceWorkerDispatcherHost::DispatchExtendableMessageEventInternal(
scoped_refptr<ServiceWorkerVersion> worker,
const base::string16& message,
const url::Origin& source_origin,
const std::vector<MessagePortChannel>& sent_message_ports,
const base::Optional<base::TimeDelta>& timeout,
const StatusCallback& callback,
const SourceInfo& source_info) {
if (!IsValidSourceInfo(source_info)) {
DidFailToDispatchExtendableMessageEvent<SourceInfo>(
sent_message_ports, source_info, callback, SERVICE_WORKER_ERROR_FAILED);
return;
}
// If not enough time is left to actually process the event don't even
// bother starting the worker and sending the event.
if (timeout && *timeout < base::TimeDelta::FromMilliseconds(100)) {
DidFailToDispatchExtendableMessageEvent<SourceInfo>(
sent_message_ports, source_info, callback,
SERVICE_WORKER_ERROR_TIMEOUT);
return;
}
worker->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::MESSAGE,
base::BindOnce(&ServiceWorkerDispatcherHost::
DispatchExtendableMessageEventAfterStartWorker,
this, worker, message, source_origin, sent_message_ports,
ExtendableMessageEventSource(source_info), timeout,
callback),
base::BindOnce(
&ServiceWorkerDispatcherHost::DidFailToDispatchExtendableMessageEvent<
SourceInfo>,
this, sent_message_ports, source_info, callback));
}
void ServiceWorkerDispatcherHost::
DispatchExtendableMessageEventAfterStartWorker(
scoped_refptr<ServiceWorkerVersion> worker,
const base::string16& message,
const url::Origin& source_origin,
const std::vector<MessagePortChannel>& sent_message_ports,
const ExtendableMessageEventSource& source,
const base::Optional<base::TimeDelta>& timeout,
const StatusCallback& callback) {
int request_id;
if (timeout) {
request_id = worker->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::MESSAGE, callback, *timeout,
ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);
} else {
request_id = worker->StartRequest(ServiceWorkerMetrics::EventType::MESSAGE,
callback);
}
mojom::ExtendableMessageEventPtr event = mojom::ExtendableMessageEvent::New();
event->message = message;
event->source_origin = source_origin;
event->message_ports = MessagePortChannel::ReleaseHandles(sent_message_ports);
event->source = source;
// Hide the client url if the client has a unique origin.
if (source_origin.unique()) {
if (event->source.client_info.IsValid())
event->source.client_info.url = GURL();
else
event->source.service_worker_info.url = GURL();
}
worker->event_dispatcher()->DispatchExtendableMessageEvent(
std::move(event), worker->CreateSimpleEventCallback(request_id));
}
template <typename SourceInfo>
void ServiceWorkerDispatcherHost::DidFailToDispatchExtendableMessageEvent(
const std::vector<MessagePortChannel>& sent_message_ports,
const SourceInfo& source_info,
const StatusCallback& callback,
ServiceWorkerStatusCode status) {
if (IsValidSourceInfo(source_info))
ReleaseSourceInfo(source_info);
callback.Run(status);
}
bool ServiceWorkerDispatcherHost::IsValidSourceInfo(
const ServiceWorkerClientInfo& source_info) {
return source_info.IsValid();
}
bool ServiceWorkerDispatcherHost::IsValidSourceInfo(
const blink::mojom::ServiceWorkerObjectInfo& source_info) {
return source_info.handle_id != blink::mojom::kInvalidServiceWorkerHandleId &&
source_info.version_id != blink::mojom::kInvalidServiceWorkerVersionId;
}
void ServiceWorkerDispatcherHost::ReleaseSourceInfo(
const ServiceWorkerClientInfo& source_info) {
// ServiceWorkerClientInfo is just a snapshot of the client. There is no need
// to do anything for it.
}
void ServiceWorkerDispatcherHost::ReleaseSourceInfo(
const blink::mojom::ServiceWorkerObjectInfo& source_info) {
ServiceWorkerHandle* handle = handles_.Lookup(source_info.handle_id);
DCHECK(handle);
handle->DecrementRefCount();
if (handle->HasNoRefCount())
handles_.Remove(source_info.handle_id);
}
void ServiceWorkerDispatcherHost::OnCountFeature(int64_t version_id,
uint32_t feature) {
if (!GetContext())
return;
ServiceWorkerVersion* version = GetContext()->GetLiveVersion(version_id);
if (!version)
return;
if (feature >=
static_cast<uint32_t>(blink::mojom::WebFeature::kNumberOfFeatures)) {
// We don't use BadMessageReceived here since this IPC will be converted to
// a Mojo method call soon, which will validate inputs for us.
// TODO(xiaofeng.zhang): Convert the OnCountFeature IPC into a Mojo method
// call.
return;
}
version->CountFeature(feature);
}
void ServiceWorkerDispatcherHost::OnIncrementServiceWorkerRefCount(
int handle_id) {
TRACE_EVENT0("ServiceWorker",
"ServiceWorkerDispatcherHost::OnIncrementServiceWorkerRefCount");
ServiceWorkerHandle* handle = handles_.Lookup(handle_id);
if (!handle) {
bad_message::ReceivedBadMessage(
this, bad_message::SWDH_INCREMENT_WORKER_BAD_HANDLE);
return;
}
handle->IncrementRefCount();
}
void ServiceWorkerDispatcherHost::OnDecrementServiceWorkerRefCount(
int handle_id) {
TRACE_EVENT0("ServiceWorker",
"ServiceWorkerDispatcherHost::OnDecrementServiceWorkerRefCount");
ServiceWorkerHandle* handle = handles_.Lookup(handle_id);
if (!handle) {
bad_message::ReceivedBadMessage(
this, bad_message::SWDH_DECREMENT_WORKER_BAD_HANDLE);
return;
}
handle->DecrementRefCount();
if (handle->HasNoRefCount())
handles_.Remove(handle_id);
}
ServiceWorkerContextCore* ServiceWorkerDispatcherHost::GetContext() {
// Temporary CHECK for debugging https://crbug.com/736203.
CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!context_wrapper_.get())
return nullptr;
return context_wrapper_->context();
}
void ServiceWorkerDispatcherHost::OnTerminateWorker(int handle_id) {
ServiceWorkerHandle* handle = handles_.Lookup(handle_id);
if (!handle) {
bad_message::ReceivedBadMessage(this,
bad_message::SWDH_TERMINATE_BAD_HANDLE);
return;
}
handle->version()->StopWorker(base::BindOnce(&base::DoNothing));
}
} // namespace content