gecko-dev/dom/presentation/provider/DisplayDeviceProvider.cpp
Shih-Chiang Chien a2a2c152ab Bug 1228526 - Part 1, support device filtering by requested presentation URL. r=smaug
MozReview-Commit-ID: JrqeavLGub1

--HG--
extra : rebase_source : fc2eca80d59dc36e97a7af8ed3de6597faf38703
2016-08-26 10:59:27 +08:00

581 lines
14 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "DisplayDeviceProvider.h"
#include "DeviceProviderHelpers.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/Unused.h"
#include "nsIObserverService.h"
#include "nsIServiceManager.h"
#include "nsIWindowWatcher.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsSimpleURI.h"
#include "nsTCPDeviceInfo.h"
#include "nsThreadUtils.h"
static mozilla::LazyLogModule gDisplayDeviceProviderLog("DisplayDeviceProvider");
#define LOG(format) MOZ_LOG(gDisplayDeviceProviderLog, mozilla::LogLevel::Debug, format)
#define DISPLAY_CHANGED_NOTIFICATION "display-changed"
#define DEFAULT_CHROME_FEATURES_PREF "toolkit.defaultChromeFeatures"
#define CHROME_REMOTE_URL_PREF "b2g.multiscreen.chrome_remote_url"
#define PREF_PRESENTATION_DISCOVERABLE_RETRY_MS "dom.presentation.discoverable.retry_ms"
namespace mozilla {
namespace dom {
namespace presentation {
/**
* This wrapper is used to break circular-reference problem.
*/
class DisplayDeviceProviderWrappedListener final
: public nsIPresentationControlServerListener
{
public:
NS_DECL_ISUPPORTS
NS_FORWARD_SAFE_NSIPRESENTATIONCONTROLSERVERLISTENER(mListener)
explicit DisplayDeviceProviderWrappedListener() = default;
nsresult SetListener(DisplayDeviceProvider* aListener)
{
mListener = aListener;
return NS_OK;
}
private:
virtual ~DisplayDeviceProviderWrappedListener() = default;
DisplayDeviceProvider* mListener = nullptr;
};
NS_IMPL_ISUPPORTS(DisplayDeviceProviderWrappedListener,
nsIPresentationControlServerListener)
NS_IMPL_ISUPPORTS(DisplayDeviceProvider::HDMIDisplayDevice,
nsIPresentationDevice,
nsIPresentationLocalDevice)
// nsIPresentationDevice
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice::GetId(nsACString& aId)
{
aId = mWindowId;
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice::GetName(nsACString& aName)
{
aName = mName;
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice::GetType(nsACString& aType)
{
aType = mType;
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice::GetWindowId(nsACString& aWindowId)
{
aWindowId = mWindowId;
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice
::EstablishControlChannel(nsIPresentationControlChannel** aControlChannel)
{
nsresult rv = OpenTopLevelWindow();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
RefPtr<DisplayDeviceProvider> provider = mProvider.get();
if (NS_WARN_IF(!provider)) {
return NS_ERROR_FAILURE;
}
return provider->Connect(this, aControlChannel);
}
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice::Disconnect()
{
nsresult rv = CloseTopLevelWindow();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;;
}
NS_IMETHODIMP
DisplayDeviceProvider::HDMIDisplayDevice::IsRequestedUrlSupported(
const nsAString& aRequestedUrl,
bool* aRetVal)
{
MOZ_ASSERT(NS_IsMainThread());
if (!aRetVal) {
return NS_ERROR_INVALID_POINTER;
}
// 1-UA device only supports HTTP/HTTPS hosted receiver page.
*aRetVal = DeviceProviderHelpers::IsCommonlySupportedScheme(aRequestedUrl);
return NS_OK;
}
nsresult
DisplayDeviceProvider::HDMIDisplayDevice::OpenTopLevelWindow()
{
MOZ_ASSERT(!mWindow);
nsresult rv;
nsAutoCString flags(Preferences::GetCString(DEFAULT_CHROME_FEATURES_PREF));
if (flags.IsEmpty()) {
return NS_ERROR_NOT_AVAILABLE;
}
flags.AppendLiteral(",mozDisplayId=");
flags.AppendInt(mScreenId);
nsAutoCString remoteShellURLString(Preferences::GetCString(CHROME_REMOTE_URL_PREF));
remoteShellURLString.AppendLiteral("#");
remoteShellURLString.Append(mWindowId);
// URI validation
nsCOMPtr<nsIURI> remoteShellURL;
rv = NS_NewURI(getter_AddRefs(remoteShellURL), remoteShellURLString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = remoteShellURL->GetSpec(remoteShellURLString);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIWindowWatcher> ww = do_GetService(NS_WINDOWWATCHER_CONTRACTID);
MOZ_ASSERT(ww);
rv = ww->OpenWindow(nullptr,
remoteShellURLString.get(),
"_blank",
flags.get(),
nullptr,
getter_AddRefs(mWindow));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
DisplayDeviceProvider::HDMIDisplayDevice::CloseTopLevelWindow()
{
MOZ_ASSERT(mWindow);
nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(mWindow);
nsresult rv = piWindow->Close();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMPL_ISUPPORTS(DisplayDeviceProvider,
nsIObserver,
nsIPresentationDeviceProvider,
nsIPresentationControlServerListener)
DisplayDeviceProvider::~DisplayDeviceProvider()
{
Uninit();
}
nsresult
DisplayDeviceProvider::Init()
{
// Provider must be initialized only once.
if (mInitialized) {
return NS_OK;
}
nsresult rv;
mServerRetryMs = Preferences::GetUint(PREF_PRESENTATION_DISCOVERABLE_RETRY_MS);
mServerRetryTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
MOZ_ASSERT(obs);
obs->AddObserver(this, DISPLAY_CHANGED_NOTIFICATION, false);
mDevice = new HDMIDisplayDevice(this);
mWrappedListener = new DisplayDeviceProviderWrappedListener();
rv = mWrappedListener->SetListener(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mPresentationService = do_CreateInstance(PRESENTATION_CONTROL_SERVICE_CONTACT_ID,
&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = StartTCPService();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mInitialized = true;
return NS_OK;
}
nsresult
DisplayDeviceProvider::Uninit()
{
// Provider must be deleted only once.
if (!mInitialized) {
return NS_OK;
}
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(this, DISPLAY_CHANGED_NOTIFICATION);
}
// Remove device from device manager when the provider is uninit
RemoveExternalScreen();
AbortServerRetry();
mInitialized = false;
mWrappedListener->SetListener(nullptr);
return NS_OK;
}
nsresult
DisplayDeviceProvider::StartTCPService()
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
rv = mPresentationService->SetId(NS_LITERAL_CSTRING("DisplayDeviceProvider"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
uint16_t servicePort;
rv = mPresentationService->GetPort(&servicePort);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
/*
* If |servicePort| is non-zero, it means PresentationServer is running.
* Otherwise, we should make it start serving.
*/
if (servicePort) {
mPort = servicePort;
return NS_OK;
}
rv = mPresentationService->SetListener(mWrappedListener);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
AbortServerRetry();
// 1-UA doesn't need encryption.
rv = mPresentationService->StartServer(/* aEncrypted = */ false,
/* aPort = */ 0);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
void
DisplayDeviceProvider::AbortServerRetry()
{
if (mIsServerRetrying) {
mIsServerRetrying = false;
mServerRetryTimer->Cancel();
}
}
nsresult
DisplayDeviceProvider::AddExternalScreen()
{
MOZ_ASSERT(mDeviceListener);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = listener->AddDevice(mDevice);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
DisplayDeviceProvider::RemoveExternalScreen()
{
MOZ_ASSERT(mDeviceListener);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = listener->RemoveDevice(mDevice);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mDevice->Disconnect();
return NS_OK;
}
// nsIPresentationDeviceProvider
NS_IMETHODIMP
DisplayDeviceProvider::GetListener(nsIPresentationDeviceListener** aListener)
{
if (NS_WARN_IF(!aListener)) {
return NS_ERROR_INVALID_POINTER;
}
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener =
do_QueryReferent(mDeviceListener, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
listener.forget(aListener);
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::SetListener(nsIPresentationDeviceListener* aListener)
{
mDeviceListener = do_GetWeakReference(aListener);
nsresult rv = mDeviceListener ? Init() : Uninit();
if(NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::ForceDiscovery()
{
return NS_OK;
}
// nsIPresentationControlServerListener
NS_IMETHODIMP
DisplayDeviceProvider::OnServerReady(uint16_t aPort,
const nsACString& aCertFingerprint)
{
MOZ_ASSERT(NS_IsMainThread());
mPort = aPort;
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::OnServerStopped(nsresult aResult)
{
MOZ_ASSERT(NS_IsMainThread());
// Try restart server if it is stopped abnormally.
if (NS_FAILED(aResult)) {
mIsServerRetrying = true;
mServerRetryTimer->Init(this, mServerRetryMs, nsITimer::TYPE_ONE_SHOT);
}
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::OnSessionRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDeviceInfo);
MOZ_ASSERT(aControlChannel);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!listener);
rv = listener->OnSessionRequest(mDevice,
aUrl,
aPresentationId,
aControlChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::OnTerminateRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel,
bool aIsFromReceiver)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDeviceInfo);
MOZ_ASSERT(aControlChannel);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!listener);
rv = listener->OnTerminateRequest(mDevice,
aPresentationId,
aControlChannel,
aIsFromReceiver);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
DisplayDeviceProvider::OnReconnectRequest(nsITCPDeviceInfo* aDeviceInfo,
const nsAString& aUrl,
const nsAString& aPresentationId,
nsIPresentationControlChannel* aControlChannel)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aDeviceInfo);
MOZ_ASSERT(aControlChannel);
nsresult rv;
nsCOMPtr<nsIPresentationDeviceListener> listener;
rv = GetListener(getter_AddRefs(listener));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MOZ_ASSERT(!listener);
rv = listener->OnReconnectRequest(mDevice,
aUrl,
aPresentationId,
aControlChannel);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
// nsIObserver
NS_IMETHODIMP
DisplayDeviceProvider::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t* aData)
{
if (!strcmp(aTopic, DISPLAY_CHANGED_NOTIFICATION)) {
nsCOMPtr<nsIDisplayInfo> displayInfo = do_QueryInterface(aSubject);
MOZ_ASSERT(displayInfo);
int32_t type;
bool isConnected;
displayInfo->GetConnected(&isConnected);
// XXX The ID is as same as the type of display.
// See Bug 1138287 and nsScreenManagerGonk::AddScreen() for more detail.
displayInfo->GetId(&type);
if (type == DisplayType::DISPLAY_EXTERNAL) {
nsresult rv = isConnected ? AddExternalScreen() : RemoveExternalScreen();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
} else if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
nsCOMPtr<nsITimer> timer = do_QueryInterface(aSubject);
if (!timer) {
return NS_ERROR_UNEXPECTED;
}
if (timer == mServerRetryTimer) {
mIsServerRetrying = false;
StartTCPService();
}
}
return NS_OK;
}
nsresult
DisplayDeviceProvider::Connect(HDMIDisplayDevice* aDevice,
nsIPresentationControlChannel** aControlChannel)
{
MOZ_ASSERT(aDevice);
MOZ_ASSERT(mPresentationService);
NS_ENSURE_ARG_POINTER(aControlChannel);
*aControlChannel = nullptr;
nsCOMPtr<nsITCPDeviceInfo> deviceInfo = new TCPDeviceInfo(aDevice->Id(),
aDevice->Address(),
mPort,
EmptyCString());
return mPresentationService->Connect(deviceInfo, aControlChannel);
}
} // namespace presentation
} // namespace dom
} // namespace mozilla