blob: 553ba64785db972824e5918f85cc96ec7ac27dc7 [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/navigator_connect/navigator_connect_context_impl.h"
#include "content/browser/message_port_service.h"
#include "content/browser/navigator_connect/service_port_service_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigator_connect_service_factory.h"
#include "content/public/common/navigator_connect_client.h"
namespace content {
struct NavigatorConnectContextImpl::Port {
// ID of this port.
int id;
// ID of the port this port is connected to.
int entangled_id;
// Service url and client origin describing this connection. These fields will
// always be the same as the same fields for the entangled port.
GURL target_url;
GURL client_origin;
// Set to nullptr when the ServicePortService goes away.
ServicePortServiceImpl* service = nullptr;
// If this port is associated with a service worker, these fields store that
// information.
int64 service_worker_registration_id = kInvalidServiceWorkerRegistrationId;
GURL service_worker_registration_origin;
};
NavigatorConnectContextImpl::NavigatorConnectContextImpl(
const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context)
: service_worker_context_(service_worker_context), next_port_id_(0) {}
NavigatorConnectContextImpl::~NavigatorConnectContextImpl() {
}
void NavigatorConnectContextImpl::AddFactory(
scoped_ptr<NavigatorConnectServiceFactory> factory) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&NavigatorConnectContextImpl::AddFactoryOnIOThread, this,
base::Passed(&factory)));
}
void NavigatorConnectContextImpl::AddFactoryOnIOThread(
scoped_ptr<NavigatorConnectServiceFactory> factory) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
service_factories_.push_back(factory.release());
}
void NavigatorConnectContextImpl::Connect(
const GURL& target_url,
const GURL& origin,
ServicePortServiceImpl* service_port_service,
const ConnectCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Create a new message channel.
int client_port_id = next_port_id_++;
int service_port_id = next_port_id_++;
Port& client_port = ports_[client_port_id];
client_port.id = client_port_id;
client_port.entangled_id = service_port_id;
client_port.target_url = target_url;
client_port.client_origin = origin;
client_port.service = service_port_service;
Port& service_port = ports_[service_port_id];
service_port.id = service_port_id;
service_port.entangled_id = client_port_id;
service_port.target_url = target_url;
service_port.client_origin = origin;
// Find the right service worker to service this connection.
service_worker_context_->FindRegistrationForDocument(
target_url,
base::Bind(&NavigatorConnectContextImpl::GotServiceWorkerRegistration,
this, callback, client_port_id, service_port_id));
}
void NavigatorConnectContextImpl::PostMessage(
int sender_port_id,
const MessagePortMessage& message,
const std::vector<TransferredMessagePort>& sent_message_ports) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(ports_.find(sender_port_id) != ports_.end());
DCHECK(message.message_as_value.empty());
const Port& sender_port = ports_[sender_port_id];
DCHECK(ports_.find(sender_port.entangled_id) != ports_.end());
const Port& port = ports_[sender_port.entangled_id];
if (port.service_worker_registration_id !=
kInvalidServiceWorkerRegistrationId) {
// Port is associated with service worker, dispatch message event via
// ServiceWorkerVersion.
// Hold messages on transferred message ports. Actual delivery of the
// message by the service can be asynchronous. When a message is delivered,
// WebMessagePortChannelImpl instances will be constructed which send
// MessagePortHostMsg_ReleaseMessages to release messages.
for (const auto& sent_port : sent_message_ports)
MessagePortService::GetInstance()->HoldMessages(sent_port.id);
service_worker_context_->FindRegistrationForId(
port.service_worker_registration_id,
port.service_worker_registration_origin,
base::Bind(&NavigatorConnectContextImpl::DeliverMessage, this, port.id,
message.message_as_string, sent_message_ports));
return;
}
if (!port.service) {
// TODO(mek): Figure out what to do in this situation.
return;
}
port.service->PostMessageToClient(port.id, message, sent_message_ports);
}
void NavigatorConnectContextImpl::GotServiceWorkerRegistration(
const ConnectCallback& callback,
int client_port_id,
int service_port_id,
ServiceWorkerStatusCode status,
const scoped_refptr<ServiceWorkerRegistration>& registration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(ports_.find(client_port_id) != ports_.end());
DCHECK(ports_.find(service_port_id) != ports_.end());
if (status != SERVICE_WORKER_OK) {
// No service worker found, reject connection attempt.
OnConnectResult(callback, client_port_id, service_port_id, registration,
status, false, base::string16(), base::string16());
return;
}
ServiceWorkerVersion* active_version = registration->active_version();
if (!active_version) {
// No active version, reject connection attempt.
OnConnectResult(callback, client_port_id, service_port_id, registration,
status, false, base::string16(), base::string16());
return;
}
Port& service_port = ports_[service_port_id];
service_port.service_worker_registration_id = registration->id();
service_port.service_worker_registration_origin =
registration->pattern().GetOrigin();
active_version->DispatchServicePortConnectEvent(
base::Bind(&NavigatorConnectContextImpl::OnConnectResult, this, callback,
client_port_id, service_port_id, registration),
service_port.target_url, service_port.client_origin, service_port_id);
}
void NavigatorConnectContextImpl::ServicePortServiceDestroyed(
ServicePortServiceImpl* service_port_service) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
for (auto& port : ports_) {
if (port.second.service != service_port_service)
continue;
port.second.service = nullptr;
// TODO(mek): Should actually inform other side of connections that the
// connection was closed, or in the case of service workers somehow keep
// track of the connection.
}
}
void NavigatorConnectContextImpl::OnConnectResult(
const ConnectCallback& callback,
int client_port_id,
int service_port_id,
const scoped_refptr<ServiceWorkerRegistration>& service_worker_registration,
ServiceWorkerStatusCode status,
bool accept_connection,
const base::string16& name,
const base::string16& data) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (accept_connection) {
// TODO(mek): Might have to do something else if the client connection got
// severed while the service side connection was being set up.
callback.Run(client_port_id, true);
} else {
// Destroy ports since connection failed.
ports_.erase(service_port_id);
ports_.erase(client_port_id);
callback.Run(MSG_ROUTING_NONE, false);
}
}
void NavigatorConnectContextImpl::DeliverMessage(
int port_id,
const base::string16& message,
const std::vector<TransferredMessagePort>& sent_message_ports,
ServiceWorkerStatusCode service_worker_status,
const scoped_refptr<ServiceWorkerRegistration>&
service_worker_registration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(ports_.find(port_id) != ports_.end());
if (service_worker_status != SERVICE_WORKER_OK) {
// TODO(mek): Do something when no service worker was found.
return;
}
ServiceWorkerVersion* active_version =
service_worker_registration->active_version();
if (!active_version) {
// TODO(mek): Do something when no active version exists.
return;
}
const Port& port = ports_[port_id];
NavigatorConnectClient client(port.target_url, port.client_origin, port_id);
active_version->DispatchCrossOriginMessageEvent(
client, message, sent_message_ports,
base::Bind(&ServiceWorkerUtils::NoOpStatusCallback));
}
} // namespace content