blob: 89f89d4fd67d1561ded2e1969e8e80c5764f2f06 [file] [log] [blame]
// Copyright 2015 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/presentation/presentation_service_impl.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/logging.h"
#include "base/stl_util.h"
#include "content/browser/presentation/presentation_type_converters.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/presentation_session_message.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/frame_navigate_params.h"
#include "content/public/common/presentation_constants.h"
namespace content {
namespace {
const int kInvalidRequestSessionId = -1;
int GetNextRequestSessionId() {
static int next_request_session_id = 0;
return ++next_request_session_id;
}
// Converts a PresentationSessionMessage |input| to a SessionMessage.
// |input|: The message to convert.
// |pass_ownership|: If true, function may reuse strings or buffers from
// |input| without copying. |input| can be freely modified.
presentation::SessionMessagePtr ToMojoSessionMessage(
content::PresentationSessionMessage* input,
bool pass_ownership) {
DCHECK(input);
presentation::SessionMessagePtr output(presentation::SessionMessage::New());
if (input->is_binary()) {
// binary data
DCHECK(input->data);
output->type = presentation::PresentationMessageType::
PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER;
if (pass_ownership) {
output->data.Swap(input->data.get());
} else {
output->data = mojo::Array<uint8_t>::From(*input->data);
}
} else {
// string message
output->type =
presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT;
if (pass_ownership) {
output->message.Swap(&input->message);
} else {
output->message = input->message;
}
}
return output.Pass();
}
scoped_ptr<PresentationSessionMessage> GetPresentationSessionMessage(
presentation::SessionMessagePtr input) {
DCHECK(!input.is_null());
scoped_ptr<content::PresentationSessionMessage> output;
switch (input->type) {
case presentation::PRESENTATION_MESSAGE_TYPE_TEXT: {
DCHECK(!input->message.is_null());
DCHECK(input->data.is_null());
// Return null PresentationSessionMessage if size exceeds.
if (input->message.size() > content::kMaxPresentationSessionMessageSize)
return output.Pass();
output.reset(
new PresentationSessionMessage(PresentationMessageType::TEXT));
input->message.Swap(&output->message);
return output.Pass();
}
case presentation::PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER: {
DCHECK(!input->data.is_null());
DCHECK(input->message.is_null());
if (input->data.size() > content::kMaxPresentationSessionMessageSize)
return output.Pass();
output.reset(new PresentationSessionMessage(
PresentationMessageType::ARRAY_BUFFER));
output->data.reset(new std::vector<uint8_t>);
input->data.Swap(output->data.get());
return output.Pass();
}
case presentation::PRESENTATION_MESSAGE_TYPE_BLOB: {
DCHECK(!input->data.is_null());
DCHECK(input->message.is_null());
if (input->data.size() > content::kMaxPresentationSessionMessageSize)
return output.Pass();
output.reset(
new PresentationSessionMessage(PresentationMessageType::BLOB));
output->data.reset(new std::vector<uint8_t>);
input->data.Swap(output->data.get());
return output.Pass();
}
}
NOTREACHED() << "Invalid presentation message type " << input->type;
return output.Pass();
}
void InvokeNewSessionMojoCallbackWithError(
const NewSessionMojoCallback& callback) {
callback.Run(
presentation::PresentationSessionInfoPtr(),
presentation::PresentationError::From(
PresentationError(PRESENTATION_ERROR_UNKNOWN, "Internal error")));
}
} // namespace
PresentationServiceImpl::PresentationServiceImpl(
RenderFrameHost* render_frame_host,
WebContents* web_contents,
PresentationServiceDelegate* delegate)
: WebContentsObserver(web_contents),
delegate_(delegate),
start_session_request_id_(kInvalidRequestSessionId),
weak_factory_(this) {
DCHECK(render_frame_host);
DCHECK(web_contents);
render_process_id_ = render_frame_host->GetProcess()->GetID();
render_frame_id_ = render_frame_host->GetRoutingID();
DVLOG(2) << "PresentationServiceImpl: "
<< render_process_id_ << ", " << render_frame_id_;
if (delegate_)
delegate_->AddObserver(render_process_id_, render_frame_id_, this);
}
PresentationServiceImpl::~PresentationServiceImpl() {
if (delegate_)
delegate_->RemoveObserver(render_process_id_, render_frame_id_);
}
// static
void PresentationServiceImpl::CreateMojoService(
RenderFrameHost* render_frame_host,
mojo::InterfaceRequest<presentation::PresentationService> request) {
DVLOG(2) << "CreateMojoService";
WebContents* web_contents =
WebContents::FromRenderFrameHost(render_frame_host);
DCHECK(web_contents);
// This object will be deleted when the RenderFrameHost is about to be
// deleted (RenderFrameDeleted) or if a connection error occurred
// (OnConnectionError).
PresentationServiceImpl* impl = new PresentationServiceImpl(
render_frame_host,
web_contents,
GetContentClient()->browser()->GetPresentationServiceDelegate(
web_contents));
impl->Bind(request.Pass());
}
void PresentationServiceImpl::Bind(
mojo::InterfaceRequest<presentation::PresentationService> request) {
binding_.reset(new mojo::Binding<presentation::PresentationService>(
this, request.Pass()));
binding_->set_connection_error_handler([this]() {
DVLOG(1) << "Connection error";
delete this;
});
}
void PresentationServiceImpl::SetClient(
presentation::PresentationServiceClientPtr client) {
DCHECK(!client_.get());
// TODO(imcheng): Set ErrorHandler to listen for errors.
client_ = client.Pass();
}
void PresentationServiceImpl::ListenForScreenAvailability(
const mojo::String& url) {
DVLOG(2) << "ListenForScreenAvailability " << url;
if (!delegate_) {
client_->OnScreenAvailabilityUpdated(url, false);
return;
}
const std::string& availability_url = url.get();
if (screen_availability_listeners_.count(availability_url))
return;
scoped_ptr<ScreenAvailabilityListenerImpl> listener(
new ScreenAvailabilityListenerImpl(availability_url, this));
if (delegate_->AddScreenAvailabilityListener(
render_process_id_,
render_frame_id_,
listener.get())) {
screen_availability_listeners_.set(availability_url, listener.Pass());
} else {
DVLOG(1) << "AddScreenAvailabilityListener failed. Ignoring request.";
}
}
void PresentationServiceImpl::StopListeningForScreenAvailability(
const mojo::String& url) {
DVLOG(2) << "StopListeningForScreenAvailability " << url;
if (!delegate_)
return;
const std::string& availability_url = url.get();
auto listener_it = screen_availability_listeners_.find(availability_url);
if (listener_it == screen_availability_listeners_.end())
return;
delegate_->RemoveScreenAvailabilityListener(
render_process_id_,
render_frame_id_,
listener_it->second);
screen_availability_listeners_.erase(listener_it);
}
void PresentationServiceImpl::ListenForDefaultSessionStart(
const DefaultSessionMojoCallback& callback) {
if (!default_session_start_context_.get())
default_session_start_context_.reset(new DefaultSessionStartContext);
default_session_start_context_->AddCallback(callback);
}
void PresentationServiceImpl::StartSession(
const mojo::String& presentation_url,
const NewSessionMojoCallback& callback) {
DVLOG(2) << "StartSession";
if (!delegate_) {
callback.Run(
presentation::PresentationSessionInfoPtr(),
presentation::PresentationError::From(
PresentationError(PRESENTATION_ERROR_NO_AVAILABLE_SCREENS,
"No screens found.")));
return;
}
// There is a StartSession request in progress. To avoid queueing up
// requests, the incoming request is rejected.
if (start_session_request_id_ != kInvalidRequestSessionId) {
InvokeNewSessionMojoCallbackWithError(callback);
return;
}
start_session_request_id_ = GetNextRequestSessionId();
pending_start_session_cb_.reset(new NewSessionMojoCallbackWrapper(callback));
delegate_->StartSession(
render_process_id_, render_frame_id_, presentation_url,
base::Bind(&PresentationServiceImpl::OnStartSessionSucceeded,
weak_factory_.GetWeakPtr(), start_session_request_id_),
base::Bind(&PresentationServiceImpl::OnStartSessionError,
weak_factory_.GetWeakPtr(), start_session_request_id_));
}
void PresentationServiceImpl::JoinSession(
const mojo::String& presentation_url,
const mojo::String& presentation_id,
const NewSessionMojoCallback& callback) {
DVLOG(2) << "JoinSession";
if (!delegate_) {
callback.Run(
presentation::PresentationSessionInfoPtr(),
presentation::PresentationError::From(
PresentationError(PRESENTATION_ERROR_NO_PRESENTATION_FOUND,
"Error joining route: No matching route")));
return;
}
int request_session_id = RegisterJoinSessionCallback(callback);
if (request_session_id == kInvalidRequestSessionId) {
InvokeNewSessionMojoCallbackWithError(callback);
return;
}
delegate_->JoinSession(
render_process_id_,
render_frame_id_,
presentation_url,
presentation_id,
base::Bind(&PresentationServiceImpl::OnJoinSessionSucceeded,
weak_factory_.GetWeakPtr(), request_session_id),
base::Bind(&PresentationServiceImpl::OnJoinSessionError,
weak_factory_.GetWeakPtr(), request_session_id));
}
int PresentationServiceImpl::RegisterJoinSessionCallback(
const NewSessionMojoCallback& callback) {
if (pending_join_session_cbs_.size() >= kMaxNumQueuedSessionRequests)
return kInvalidRequestSessionId;
int request_id = GetNextRequestSessionId();
pending_join_session_cbs_[request_id].reset(
new NewSessionMojoCallbackWrapper(callback));
return request_id;
}
void PresentationServiceImpl::OnStartSessionSucceeded(
int request_session_id,
const PresentationSessionInfo& session_info) {
if (request_session_id == start_session_request_id_) {
CHECK(pending_start_session_cb_.get());
pending_start_session_cb_->Run(
presentation::PresentationSessionInfo::From(session_info),
presentation::PresentationErrorPtr());
pending_start_session_cb_.reset();
start_session_request_id_ = kInvalidRequestSessionId;
}
}
void PresentationServiceImpl::OnStartSessionError(
int request_session_id,
const PresentationError& error) {
if (request_session_id == start_session_request_id_) {
CHECK(pending_start_session_cb_.get());
pending_start_session_cb_->Run(
presentation::PresentationSessionInfoPtr(),
presentation::PresentationError::From(error));
pending_start_session_cb_.reset();
start_session_request_id_ = kInvalidRequestSessionId;
}
}
void PresentationServiceImpl::OnJoinSessionSucceeded(
int request_session_id,
const PresentationSessionInfo& session_info) {
RunAndEraseJoinSessionMojoCallback(
request_session_id,
presentation::PresentationSessionInfo::From(session_info),
presentation::PresentationErrorPtr());
}
void PresentationServiceImpl::OnJoinSessionError(
int request_session_id,
const PresentationError& error) {
RunAndEraseJoinSessionMojoCallback(
request_session_id,
presentation::PresentationSessionInfoPtr(),
presentation::PresentationError::From(error));
}
void PresentationServiceImpl::RunAndEraseJoinSessionMojoCallback(
int request_session_id,
presentation::PresentationSessionInfoPtr session,
presentation::PresentationErrorPtr error) {
auto it = pending_join_session_cbs_.find(request_session_id);
if (it == pending_join_session_cbs_.end())
return;
DCHECK(it->second.get());
it->second->Run(session.Pass(), error.Pass());
pending_join_session_cbs_.erase(it);
}
void PresentationServiceImpl::SetDefaultPresentationURL(
const mojo::String& url) {
DVLOG(2) << "SetDefaultPresentationURL";
if (!delegate_)
return;
const std::string& new_default_url = url.get();
if (default_presentation_url_ == new_default_url)
return;
delegate_->SetDefaultPresentationUrl(
render_process_id_,
render_frame_id_,
new_default_url);
default_presentation_url_ = new_default_url;
}
void PresentationServiceImpl::SendSessionMessage(
presentation::PresentationSessionInfoPtr session,
presentation::SessionMessagePtr session_message,
const SendMessageMojoCallback& callback) {
DVLOG(2) << "SendSessionMessage";
DCHECK(!session_message.is_null());
// send_message_callback_ should be null by now, otherwise resetting of
// send_message_callback_ with new callback will drop the old callback.
if (!delegate_ || send_message_callback_) {
callback.Run(false);
return;
}
send_message_callback_.reset(new SendMessageMojoCallback(callback));
delegate_->SendMessage(
render_process_id_, render_frame_id_,
session.To<PresentationSessionInfo>(),
GetPresentationSessionMessage(session_message.Pass()),
base::Bind(&PresentationServiceImpl::OnSendMessageCallback,
weak_factory_.GetWeakPtr()));
}
void PresentationServiceImpl::OnSendMessageCallback(bool sent) {
// It is possible that Reset() is invoked before receiving this callback.
// So, always check send_message_callback_ for non-null.
if (send_message_callback_) {
send_message_callback_->Run(sent);
send_message_callback_.reset();
}
}
void PresentationServiceImpl::CloseSession(
const mojo::String& presentation_url,
const mojo::String& presentation_id) {
DVLOG(2) << "CloseSession " << presentation_id;
if (delegate_)
delegate_->CloseSession(render_process_id_, render_frame_id_,
presentation_id);
}
void PresentationServiceImpl::ListenForSessionStateChange() {
if (!delegate_)
return;
delegate_->ListenForSessionStateChange(
render_process_id_, render_frame_id_,
base::Bind(&PresentationServiceImpl::OnSessionStateChanged,
weak_factory_.GetWeakPtr()));
}
void PresentationServiceImpl::OnSessionStateChanged(
const PresentationSessionInfo& session_info,
PresentationSessionState session_state) {
DCHECK(client_.get());
client_->OnSessionStateChanged(
presentation::PresentationSessionInfo::From(session_info),
PresentationSessionStateToMojo(session_state));
}
bool PresentationServiceImpl::FrameMatches(
content::RenderFrameHost* render_frame_host) const {
if (!render_frame_host)
return false;
return render_frame_host->GetProcess()->GetID() == render_process_id_ &&
render_frame_host->GetRoutingID() == render_frame_id_;
}
void PresentationServiceImpl::ListenForSessionMessages(
presentation::PresentationSessionInfoPtr session) {
DVLOG(2) << "ListenForSessionMessages";
if (!delegate_)
return;
PresentationSessionInfo session_info(session.To<PresentationSessionInfo>());
delegate_->ListenForSessionMessages(
render_process_id_, render_frame_id_, session_info,
base::Bind(&PresentationServiceImpl::OnSessionMessages,
weak_factory_.GetWeakPtr(), session_info));
}
void PresentationServiceImpl::OnSessionMessages(
const PresentationSessionInfo& session,
const ScopedVector<PresentationSessionMessage>& messages,
bool pass_ownership) {
DCHECK(client_);
DVLOG(2) << "OnSessionMessages";
mojo::Array<presentation::SessionMessagePtr> mojoMessages(messages.size());
for (size_t i = 0; i < messages.size(); ++i)
mojoMessages[i] = ToMojoSessionMessage(messages[i], pass_ownership);
client_->OnSessionMessagesReceived(
presentation::PresentationSessionInfo::From(session),
mojoMessages.Pass());
}
void PresentationServiceImpl::DidNavigateAnyFrame(
content::RenderFrameHost* render_frame_host,
const content::LoadCommittedDetails& details,
const content::FrameNavigateParams& params) {
DVLOG(2) << "PresentationServiceImpl::DidNavigateAnyFrame";
if (!FrameMatches(render_frame_host))
return;
std::string prev_url_host = details.previous_url.host();
std::string curr_url_host = params.url.host();
// If a frame navigation is in-page (e.g. navigating to a fragment in
// same page) then we do not unregister listeners.
DVLOG(2) << "DidNavigateAnyFrame: "
<< "prev host: " << prev_url_host << ", curr host: " << curr_url_host
<< ", details.is_in_page: " << details.is_in_page;
if (details.is_in_page)
return;
// Reset if the frame actually navigated.
Reset();
}
void PresentationServiceImpl::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
DVLOG(2) << "PresentationServiceImpl::RenderFrameDeleted";
if (!FrameMatches(render_frame_host))
return;
// RenderFrameDeleted means the associated RFH is going to be deleted soon.
// This object should also be deleted.
Reset();
delete this;
}
void PresentationServiceImpl::Reset() {
DVLOG(2) << "PresentationServiceImpl::Reset";
if (delegate_)
delegate_->Reset(render_process_id_, render_frame_id_);
default_presentation_url_.clear();
screen_availability_listeners_.clear();
start_session_request_id_ = kInvalidRequestSessionId;
pending_start_session_cb_.reset();
pending_join_session_cbs_.clear();
default_session_start_context_.reset();
if (on_session_messages_callback_.get()) {
on_session_messages_callback_->Run(
mojo::Array<presentation::SessionMessagePtr>());
on_session_messages_callback_.reset();
}
if (send_message_callback_) {
// Run the callback with false, indicating the renderer to stop sending
// the requests and invalidate all pending requests.
send_message_callback_->Run(false);
send_message_callback_.reset();
}
}
void PresentationServiceImpl::OnDelegateDestroyed() {
DVLOG(2) << "PresentationServiceImpl::OnDelegateDestroyed";
delegate_ = nullptr;
Reset();
}
void PresentationServiceImpl::OnDefaultPresentationStarted(
const PresentationSessionInfo& session) {
if (default_session_start_context_.get())
default_session_start_context_->set_session(session);
}
PresentationServiceImpl::ScreenAvailabilityListenerImpl
::ScreenAvailabilityListenerImpl(
const std::string& availability_url,
PresentationServiceImpl* service)
: availability_url_(availability_url),
service_(service) {
DCHECK(service_);
DCHECK(service_->client_.get());
}
PresentationServiceImpl::ScreenAvailabilityListenerImpl::
~ScreenAvailabilityListenerImpl() {
}
std::string PresentationServiceImpl::ScreenAvailabilityListenerImpl
::GetAvailabilityUrl() const {
return availability_url_;
}
void PresentationServiceImpl::ScreenAvailabilityListenerImpl
::OnScreenAvailabilityChanged(bool available) {
service_->client_->OnScreenAvailabilityUpdated(availability_url_, available);
}
void PresentationServiceImpl::ScreenAvailabilityListenerImpl
::OnScreenAvailabilityNotSupported() {
service_->client_->OnScreenAvailabilityNotSupported(availability_url_);
}
PresentationServiceImpl::NewSessionMojoCallbackWrapper
::NewSessionMojoCallbackWrapper(const NewSessionMojoCallback& callback)
: callback_(callback) {
}
PresentationServiceImpl::NewSessionMojoCallbackWrapper
::~NewSessionMojoCallbackWrapper() {
if (!callback_.is_null())
InvokeNewSessionMojoCallbackWithError(callback_);
}
void PresentationServiceImpl::NewSessionMojoCallbackWrapper::Run(
presentation::PresentationSessionInfoPtr session,
presentation::PresentationErrorPtr error) {
DCHECK(!callback_.is_null());
callback_.Run(session.Pass(), error.Pass());
callback_.reset();
}
PresentationServiceImpl::DefaultSessionStartContext
::DefaultSessionStartContext() {
}
PresentationServiceImpl::DefaultSessionStartContext
::~DefaultSessionStartContext() {
Reset();
}
void PresentationServiceImpl::DefaultSessionStartContext::AddCallback(
const DefaultSessionMojoCallback& callback) {
if (session_.get()) {
DCHECK(callbacks_.empty());
callback.Run(presentation::PresentationSessionInfo::From(*session_));
session_.reset();
} else {
callbacks_.push_back(new DefaultSessionMojoCallback(callback));
}
}
void PresentationServiceImpl::DefaultSessionStartContext::set_session(
const PresentationSessionInfo& session) {
if (callbacks_.empty()) {
session_.reset(new PresentationSessionInfo(session));
} else {
DCHECK(!session_.get());
ScopedVector<DefaultSessionMojoCallback> callbacks;
callbacks.swap(callbacks_);
for (const auto& callback : callbacks)
callback->Run(presentation::PresentationSessionInfo::From(session));
}
}
void PresentationServiceImpl::DefaultSessionStartContext::Reset() {
ScopedVector<DefaultSessionMojoCallback> callbacks;
callbacks.swap(callbacks_);
for (const auto& callback : callbacks)
callback->Run(presentation::PresentationSessionInfoPtr());
session_.reset();
}
} // namespace content