blob: ac7e6b05ce9a3237d908ad6d2362c5e236dcc628 [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/renderer/presentation/presentation_dispatcher.h"
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/public/renderer/render_frame.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "third_party/WebKit/public/platform/WebURL.h"
#include "third_party/WebKit/public/platform/WebVector.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationAvailabilityObserver.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationConnection.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationConnectionCallbacks.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationController.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationError.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationInfo.h"
#include "third_party/WebKit/public/platform/modules/presentation/WebPresentationReceiver.h"
#include "third_party/WebKit/public/platform/modules/presentation/presentation.mojom.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "url/gurl.h"
namespace content {
namespace {
blink::WebPresentationError::ErrorType GetWebPresentationErrorType(
PresentationErrorType errorType) {
switch (errorType) {
case PresentationErrorType::PRESENTATION_ERROR_NO_AVAILABLE_SCREENS:
return blink::WebPresentationError::kErrorTypeNoAvailableScreens;
case PresentationErrorType::
PRESENTATION_ERROR_PRESENTATION_REQUEST_CANCELLED:
return blink::WebPresentationError::
kErrorTypePresentationRequestCancelled;
case PresentationErrorType::PRESENTATION_ERROR_NO_PRESENTATION_FOUND:
return blink::WebPresentationError::kErrorTypeNoPresentationFound;
case PresentationErrorType::PRESENTATION_ERROR_PREVIOUS_START_IN_PROGRESS:
return blink::WebPresentationError::kErrorTypePreviousStartInProgress;
case PresentationErrorType::PRESENTATION_ERROR_UNKNOWN:
default:
return blink::WebPresentationError::kErrorTypeUnknown;
}
}
blink::WebPresentationConnectionState GetWebPresentationConnectionState(
PresentationConnectionState connectionState) {
switch (connectionState) {
case PresentationConnectionState::PRESENTATION_CONNECTION_STATE_CONNECTING:
return blink::WebPresentationConnectionState::kConnecting;
case PresentationConnectionState::PRESENTATION_CONNECTION_STATE_CONNECTED:
return blink::WebPresentationConnectionState::kConnected;
case PresentationConnectionState::PRESENTATION_CONNECTION_STATE_CLOSED:
return blink::WebPresentationConnectionState::kClosed;
case PresentationConnectionState::PRESENTATION_CONNECTION_STATE_TERMINATED:
return blink::WebPresentationConnectionState::kTerminated;
default:
NOTREACHED();
return blink::WebPresentationConnectionState::kTerminated;
}
}
blink::WebPresentationConnectionCloseReason
GetWebPresentationConnectionCloseReason(
PresentationConnectionCloseReason connectionCloseReason) {
switch (connectionCloseReason) {
case PresentationConnectionCloseReason::
PRESENTATION_CONNECTION_CLOSE_REASON_CONNECTION_ERROR:
return blink::WebPresentationConnectionCloseReason::kError;
case PresentationConnectionCloseReason::
PRESENTATION_CONNECTION_CLOSE_REASON_CLOSED:
return blink::WebPresentationConnectionCloseReason::kClosed;
case PresentationConnectionCloseReason::
PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY:
return blink::WebPresentationConnectionCloseReason::kWentAway;
default:
NOTREACHED();
return blink::WebPresentationConnectionCloseReason::kError;
}
}
} // namespace
// TODO(mfoltz): Reorder definitions to match presentation_dispatcher.h.
PresentationDispatcher::PresentationDispatcher(RenderFrame* render_frame)
: RenderFrameObserver(render_frame),
controller_(nullptr),
receiver_(nullptr),
binding_(this) {}
PresentationDispatcher::~PresentationDispatcher() {
// Controller should be destroyed before the dispatcher when frame is
// destroyed.
DCHECK(!controller_);
}
void PresentationDispatcher::SetController(
blink::WebPresentationController* controller) {
// There shouldn't be any swapping from one non-null controller to another.
DCHECK(controller != controller_ && (!controller || !controller_));
controller_ = controller;
// The controller is set to null when the frame is about to be detached.
// Nothing is listening for screen availability anymore but the Mojo service
// will know about the frame being detached anyway.
}
void PresentationDispatcher::StartPresentation(
const blink::WebVector<blink::WebURL>& presentationUrls,
std::unique_ptr<blink::WebPresentationConnectionCallbacks> callback) {
DCHECK(callback);
ConnectToPresentationServiceIfNeeded();
std::vector<GURL> urls;
for (const auto& url : presentationUrls)
urls.push_back(url);
// The dispatcher owns the service so |this| will be valid when
// OnConnectionCreated() is called. |callback| needs to be alive and also
// needs to be destroyed so we transfer its ownership to the mojo callback.
presentation_service_->StartPresentation(
urls, base::BindOnce(&PresentationDispatcher::OnConnectionCreated,
base::Unretained(this), base::Passed(&callback)));
}
void PresentationDispatcher::ReconnectPresentation(
const blink::WebVector<blink::WebURL>& presentationUrls,
const blink::WebString& presentationId,
std::unique_ptr<blink::WebPresentationConnectionCallbacks> callback) {
DCHECK(callback);
ConnectToPresentationServiceIfNeeded();
std::vector<GURL> urls;
for (const auto& url : presentationUrls)
urls.push_back(url);
// The dispatcher owns the service so |this| will be valid when
// OnConnectionCreated() is called. |callback| needs to be alive and also
// needs to be destroyed so we transfer its ownership to the mojo callback.
presentation_service_->ReconnectPresentation(
urls, presentationId.Utf8(),
base::BindOnce(&PresentationDispatcher::OnConnectionCreated,
base::Unretained(this), base::Passed(&callback)));
}
void PresentationDispatcher::GetAvailability(
const blink::WebVector<blink::WebURL>& availabilityUrls,
std::unique_ptr<blink::WebPresentationAvailabilityCallbacks> callback) {
DCHECK(!availabilityUrls.IsEmpty());
std::vector<GURL> urls;
for (const auto& availability_url : availabilityUrls)
urls.push_back(availability_url);
auto screen_availability = GetScreenAvailability(urls);
// Reject Promise if screen availability is unsupported for all URLs.
if (screen_availability == blink::mojom::ScreenAvailability::DISABLED) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&blink::WebPresentationAvailabilityCallbacks::OnError,
base::Passed(&callback),
blink::WebPresentationError(
blink::WebPresentationError::kErrorTypeAvailabilityNotSupported,
"Screen availability monitoring not supported")));
// Do not listen to urls if we reject the promise.
return;
}
auto* listener = GetAvailabilityListener(urls);
if (!listener) {
listener = new AvailabilityListener(urls);
availability_set_.insert(base::WrapUnique(listener));
}
if (screen_availability != blink::mojom::ScreenAvailability::UNKNOWN) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&blink::WebPresentationAvailabilityCallbacks::OnSuccess,
base::Passed(&callback),
screen_availability ==
blink::mojom::ScreenAvailability::AVAILABLE));
} else {
listener->availability_callbacks.Add(std::move(callback));
}
for (const auto& availabilityUrl : urls)
StartListeningToURL(availabilityUrl);
}
void PresentationDispatcher::StartListening(
blink::WebPresentationAvailabilityObserver* observer) {
std::vector<GURL> urls;
for (const auto& url : observer->Urls())
urls.push_back(url);
auto* listener = GetAvailabilityListener(urls);
if (!listener) {
listener = new AvailabilityListener(urls);
availability_set_.insert(base::WrapUnique(listener));
}
listener->availability_observers.insert(observer);
for (const auto& availabilityUrl : urls)
StartListeningToURL(availabilityUrl);
}
void PresentationDispatcher::StopListening(
blink::WebPresentationAvailabilityObserver* observer) {
std::vector<GURL> urls;
for (const auto& url : observer->Urls())
urls.push_back(url);
auto* listener = GetAvailabilityListener(urls);
if (!listener) {
DLOG(WARNING) << "Stop listening for availability for unknown URLs.";
return;
}
listener->availability_observers.erase(observer);
for (const auto& availabilityUrl : urls)
MaybeStopListeningToURL(availabilityUrl);
TryRemoveAvailabilityListener(listener);
}
void PresentationDispatcher::SetDefaultPresentationUrls(
const blink::WebVector<blink::WebURL>& presentationUrls) {
ConnectToPresentationServiceIfNeeded();
std::vector<GURL> urls;
for (const auto& url : presentationUrls)
urls.push_back(url);
presentation_service_->SetDefaultPresentationUrls(urls);
}
void PresentationDispatcher::SetReceiver(
blink::WebPresentationReceiver* receiver) {
receiver_ = receiver;
DCHECK(!render_frame() || render_frame()->IsMainFrame());
// Init |receiver_| after loading document.
if (receiver_ && render_frame() && render_frame()->GetWebFrame() &&
!render_frame()->GetWebFrame()->IsLoading()) {
receiver_->Init();
}
}
void PresentationDispatcher::DidFinishDocumentLoad() {
if (receiver_)
receiver_->Init();
}
void PresentationDispatcher::OnDestruct() {
delete this;
}
void PresentationDispatcher::WidgetWillClose() {
if (receiver_)
receiver_->OnReceiverTerminated();
}
void PresentationDispatcher::OnScreenAvailabilityUpdated(
const GURL& url,
blink::mojom::ScreenAvailability availability) {
auto* listening_status = GetListeningStatus(url);
if (!listening_status)
return;
if (listening_status->listening_state == ListeningState::WAITING)
listening_status->listening_state = ListeningState::ACTIVE;
if (listening_status->last_known_availability == availability)
return;
listening_status->last_known_availability = availability;
static const blink::WebString& not_supported_error =
blink::WebString::FromUTF8(
"getAvailability() isn't supported at the moment. It can be due to "
"a permanent or temporary system limitation. It is recommended to "
"try to blindly start a presentation in that case.");
std::set<AvailabilityListener*> modified_listeners;
for (auto& listener : availability_set_) {
if (!base::ContainsValue(listener->urls, url))
continue;
auto screen_availability = GetScreenAvailability(listener->urls);
DCHECK(screen_availability != blink::mojom::ScreenAvailability::UNKNOWN);
for (auto* observer : listener->availability_observers)
observer->AvailabilityChanged(screen_availability);
for (AvailabilityCallbacksMap::iterator iter(
&listener->availability_callbacks);
!iter.IsAtEnd(); iter.Advance()) {
if (screen_availability == blink::mojom::ScreenAvailability::DISABLED) {
iter.GetCurrentValue()->OnError(blink::WebPresentationError(
blink::WebPresentationError::kErrorTypeAvailabilityNotSupported,
not_supported_error));
} else {
iter.GetCurrentValue()->OnSuccess(
screen_availability == blink::mojom::ScreenAvailability::AVAILABLE);
}
}
listener->availability_callbacks.Clear();
for (const auto& availabilityUrl : listener->urls)
MaybeStopListeningToURL(availabilityUrl);
modified_listeners.insert(listener.get());
}
for (auto* listener : modified_listeners)
TryRemoveAvailabilityListener(listener);
}
void PresentationDispatcher::OnDefaultPresentationStarted(
const PresentationInfo& presentation_info) {
if (!controller_)
return;
auto* connection =
controller_->DidStartDefaultPresentation(blink::WebPresentationInfo(
presentation_info.presentation_url,
blink::WebString::FromUTF8(presentation_info.presentation_id)));
if (connection)
connection->Init();
}
void PresentationDispatcher::OnConnectionCreated(
std::unique_ptr<blink::WebPresentationConnectionCallbacks> callback,
const base::Optional<PresentationInfo>& presentation_info,
const base::Optional<PresentationError>& error) {
DCHECK(callback);
if (error) {
DCHECK(!presentation_info);
callback->OnError(blink::WebPresentationError(
GetWebPresentationErrorType(error->error_type),
blink::WebString::FromUTF8(error->message)));
return;
}
DCHECK(presentation_info);
callback->OnSuccess(blink::WebPresentationInfo(
presentation_info->presentation_url,
blink::WebString::FromUTF8(presentation_info->presentation_id)));
auto* connection = callback->GetConnection();
if (connection)
connection->Init();
}
void PresentationDispatcher::OnConnectionStateChanged(
const PresentationInfo& presentation_info,
PresentationConnectionState state) {
if (!controller_)
return;
controller_->DidChangeConnectionState(
blink::WebPresentationInfo(
presentation_info.presentation_url,
blink::WebString::FromUTF8(presentation_info.presentation_id)),
GetWebPresentationConnectionState(state));
}
void PresentationDispatcher::OnConnectionClosed(
const PresentationInfo& presentation_info,
PresentationConnectionCloseReason reason,
const std::string& message) {
if (!controller_)
return;
controller_->DidCloseConnection(
blink::WebPresentationInfo(
presentation_info.presentation_url,
blink::WebString::FromUTF8(presentation_info.presentation_id)),
GetWebPresentationConnectionCloseReason(reason),
blink::WebString::FromUTF8(message));
}
void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
if (presentation_service_)
return;
render_frame()->GetRemoteInterfaces()->GetInterface(&presentation_service_);
blink::mojom::PresentationServiceClientPtr client;
binding_.Bind(mojo::MakeRequest(&client));
presentation_service_->SetClient(std::move(client));
}
void PresentationDispatcher::StartListeningToURL(const GURL& url) {
auto* listening_status = GetListeningStatus(url);
if (!listening_status) {
listening_status = new ListeningStatus(url);
listening_status_.insert(
std::make_pair(url, base::WrapUnique(listening_status)));
}
// Already listening.
if (listening_status->listening_state != ListeningState::INACTIVE)
return;
ConnectToPresentationServiceIfNeeded();
listening_status->listening_state = ListeningState::WAITING;
presentation_service_->ListenForScreenAvailability(url);
}
void PresentationDispatcher::MaybeStopListeningToURL(const GURL& url) {
for (const auto& listener : availability_set_) {
if (!base::ContainsValue(listener->urls, url))
continue;
// URL is still observed by some availability object.
if (!listener->availability_callbacks.IsEmpty() ||
!listener->availability_observers.empty()) {
return;
}
}
auto* listening_status = GetListeningStatus(url);
if (!listening_status) {
LOG(WARNING) << "Stop listening to unknown url: " << url;
return;
}
if (listening_status->listening_state == ListeningState::INACTIVE)
return;
ConnectToPresentationServiceIfNeeded();
listening_status->listening_state = ListeningState::INACTIVE;
presentation_service_->StopListeningForScreenAvailability(url);
}
PresentationDispatcher::ListeningStatus*
PresentationDispatcher::GetListeningStatus(const GURL& url) const {
auto status_it = listening_status_.find(url);
return status_it == listening_status_.end() ? nullptr
: status_it->second.get();
}
PresentationDispatcher::AvailabilityListener*
PresentationDispatcher::GetAvailabilityListener(
const std::vector<GURL>& urls) const {
auto listener_it =
std::find_if(availability_set_.begin(), availability_set_.end(),
[&urls](const std::unique_ptr<AvailabilityListener>& x) {
return x->urls == urls;
});
return listener_it == availability_set_.end() ? nullptr : listener_it->get();
}
void PresentationDispatcher::TryRemoveAvailabilityListener(
AvailabilityListener* listener) {
// URL is still observed by some availability object.
if (!listener->availability_callbacks.IsEmpty() ||
!listener->availability_observers.empty()) {
return;
}
auto listener_it = availability_set_.begin();
while (listener_it != availability_set_.end()) {
if (listener_it->get() == listener) {
availability_set_.erase(listener_it);
return;
} else {
++listener_it;
}
}
}
blink::mojom::ScreenAvailability PresentationDispatcher::GetScreenAvailability(
const std::vector<GURL>& urls) const {
bool has_disabled = false;
bool has_source_not_supported = false;
bool has_unavailable = false;
for (const auto& url : urls) {
auto* status = GetListeningStatus(url);
auto screen_availability = status
? status->last_known_availability
: blink::mojom::ScreenAvailability::UNKNOWN;
if (screen_availability == blink::mojom::ScreenAvailability::AVAILABLE) {
return blink::mojom::ScreenAvailability::AVAILABLE;
} else if (screen_availability ==
blink::mojom::ScreenAvailability::DISABLED) {
has_disabled = true;
} else if (screen_availability ==
blink::mojom::ScreenAvailability::SOURCE_NOT_SUPPORTED) {
has_source_not_supported = true;
} else if (screen_availability ==
blink::mojom::ScreenAvailability::UNAVAILABLE) {
has_unavailable = true;
}
}
if (has_disabled)
return blink::mojom::ScreenAvailability::DISABLED;
if (has_source_not_supported)
return blink::mojom::ScreenAvailability::SOURCE_NOT_SUPPORTED;
if (has_unavailable)
return blink::mojom::ScreenAvailability::UNAVAILABLE;
return blink::mojom::ScreenAvailability::UNKNOWN;
}
PresentationDispatcher::AvailabilityListener::AvailabilityListener(
const std::vector<GURL>& availability_urls)
: urls(availability_urls) {}
PresentationDispatcher::AvailabilityListener::~AvailabilityListener() {}
PresentationDispatcher::ListeningStatus::ListeningStatus(
const GURL& availability_url)
: url(availability_url),
last_known_availability(blink::mojom::ScreenAvailability::UNKNOWN),
listening_state(ListeningState::INACTIVE) {}
PresentationDispatcher::ListeningStatus::~ListeningStatus() {}
} // namespace content