diff options
Diffstat (limited to 'dom/presentation/provider/DisplayDeviceProvider.cpp')
-rw-r--r-- | dom/presentation/provider/DisplayDeviceProvider.cpp | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/dom/presentation/provider/DisplayDeviceProvider.cpp b/dom/presentation/provider/DisplayDeviceProvider.cpp new file mode 100644 index 000000000..3f88aba5e --- /dev/null +++ b/dom/presentation/provider/DisplayDeviceProvider.cpp @@ -0,0 +1,580 @@ +/* -*- 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 |