summaryrefslogtreecommitdiffstats
path: root/dom/presentation/provider/DisplayDeviceProvider.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/presentation/provider/DisplayDeviceProvider.cpp')
-rw-r--r--dom/presentation/provider/DisplayDeviceProvider.cpp580
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