/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=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 "PresentationService.h" #include "ipc/PresentationIPCService.h" #include "mozilla/Services.h" #include "nsArrayUtils.h" #include "nsGlobalWindow.h" #include "nsIMutableArray.h" #include "nsIObserverService.h" #include "nsIPresentationDeviceManager.h" #include "nsIPresentationDevicePrompt.h" #include "nsIPresentationListener.h" #include "nsIPresentationRequestUIGlue.h" #include "nsIPresentationSessionRequest.h" #include "nsIPresentationTerminateRequest.h" #include "nsISupportsPrimitives.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsXPCOMCID.h" #include "nsXULAppAPI.h" #include "PresentationLog.h" namespace mozilla { namespace dom { static bool IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnother) { if (!aDevice || !aDeviceAnother) { return false; } nsAutoCString deviceId; aDevice->GetId(deviceId); nsAutoCString anotherId; aDeviceAnother->GetId(anotherId); if (!deviceId.Equals(anotherId)) { return false; } nsAutoCString deviceType; aDevice->GetType(deviceType); nsAutoCString anotherType; aDeviceAnother->GetType(anotherType); if (!deviceType.Equals(anotherType)) { return false; } return true; } static nsresult ConvertURLArrayHelper(const nsTArray& aUrls, nsIArray** aResult) { if (!aResult) { return NS_ERROR_INVALID_POINTER; } *aResult = nullptr; nsresult rv; nsCOMPtr urls = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } for (const auto& url : aUrls) { nsCOMPtr isupportsString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = isupportsString->SetData(url); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = urls->AppendElement(isupportsString); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } urls.forget(aResult); return NS_OK; } /* * Implementation of PresentationDeviceRequest */ class PresentationDeviceRequest final : public nsIPresentationDeviceRequest { public: NS_DECL_ISUPPORTS NS_DECL_NSIPRESENTATIONDEVICEREQUEST PresentationDeviceRequest( const nsTArray& aUrls, const nsAString& aId, const nsAString& aOrigin, uint64_t aWindowId, EventTarget* aEventTarget, nsIPrincipal* aPrincipal, nsIPresentationServiceCallback* aCallback, nsIPresentationTransportBuilderConstructor* aBuilderConstructor); private: virtual ~PresentationDeviceRequest() = default; nsresult CreateSessionInfo(nsIPresentationDevice* aDevice, const nsAString& aSelectedRequestUrl); nsTArray mRequestUrls; nsString mId; nsString mOrigin; uint64_t mWindowId; nsWeakPtr mChromeEventHandler; nsCOMPtr mPrincipal; nsCOMPtr mCallback; nsCOMPtr mBuilderConstructor; }; LazyLogModule gPresentationLog("Presentation"); NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest) PresentationDeviceRequest::PresentationDeviceRequest( const nsTArray& aUrls, const nsAString& aId, const nsAString& aOrigin, uint64_t aWindowId, EventTarget* aEventTarget, nsIPrincipal* aPrincipal, nsIPresentationServiceCallback* aCallback, nsIPresentationTransportBuilderConstructor* aBuilderConstructor) : mRequestUrls(aUrls.Clone()), mId(aId), mOrigin(aOrigin), mWindowId(aWindowId), mChromeEventHandler(do_GetWeakReference(aEventTarget)), mPrincipal(aPrincipal), mCallback(aCallback), mBuilderConstructor(aBuilderConstructor) { MOZ_ASSERT(!mRequestUrls.IsEmpty()); MOZ_ASSERT(!mId.IsEmpty()); MOZ_ASSERT(!mOrigin.IsEmpty()); MOZ_ASSERT(mCallback); MOZ_ASSERT(mBuilderConstructor); } NS_IMETHODIMP PresentationDeviceRequest::GetOrigin(nsAString& aOrigin) { aOrigin = mOrigin; return NS_OK; } NS_IMETHODIMP PresentationDeviceRequest::GetRequestURLs(nsIArray** aUrls) { return ConvertURLArrayHelper(mRequestUrls, aUrls); } NS_IMETHODIMP PresentationDeviceRequest::GetChromeEventHandler( EventTarget** aChromeEventHandler) { RefPtr handler(do_QueryReferent(mChromeEventHandler)); handler.forget(aChromeEventHandler); return NS_OK; } NS_IMETHODIMP PresentationDeviceRequest::GetPrincipal(nsIPrincipal** aPrincipal) { nsCOMPtr principal(mPrincipal); principal.forget(aPrincipal); return NS_OK; } NS_IMETHODIMP PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice) { MOZ_ASSERT(NS_IsMainThread()); if (NS_WARN_IF(!aDevice)) { MOZ_ASSERT(false, "|aDevice| should noe be null."); mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); return NS_ERROR_INVALID_ARG; } // Select the most suitable URL for starting the presentation. nsAutoString selectedRequestUrl; for (const auto& url : mRequestUrls) { bool isSupported; if (NS_SUCCEEDED(aDevice->IsRequestedUrlSupported(url, &isSupported)) && isSupported) { selectedRequestUrl.Assign(url); break; } } if (selectedRequestUrl.IsEmpty()) { return mCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); } if (NS_WARN_IF(NS_FAILED(CreateSessionInfo(aDevice, selectedRequestUrl)))) { return mCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } return mCallback->NotifySuccess(selectedRequestUrl); } nsresult PresentationDeviceRequest::CreateSessionInfo( nsIPresentationDevice* aDevice, const nsAString& aSelectedRequestUrl) { nsCOMPtr service = do_GetService(PRESENTATION_SERVICE_CONTRACTID); if (NS_WARN_IF(!service)) { return NS_ERROR_NOT_AVAILABLE; } // Create the controlling session info RefPtr info = static_cast(service.get()) ->CreateControllingSessionInfo(aSelectedRequestUrl, mId, mWindowId); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } info->SetDevice(aDevice); // Establish a control channel. If we failed to do so, the callback is called // with an error message. nsCOMPtr ctrlChannel; nsresult rv = aDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel)); if (NS_WARN_IF(NS_FAILED(rv))) { return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR); } // Initialize the session info with the control channel. rv = info->Init(ctrlChannel); if (NS_WARN_IF(NS_FAILED(rv))) { return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR); } info->SetTransportBuilderConstructor(mBuilderConstructor); return NS_OK; } NS_IMETHODIMP PresentationDeviceRequest::Cancel(nsresult aReason) { return mCallback->NotifyError(aReason); } /* * Implementation of PresentationService */ NS_IMPL_ISUPPORTS(PresentationService, nsIPresentationService, nsIObserver) PresentationService::PresentationService() = default; PresentationService::~PresentationService() { HandleShutdown(); } bool PresentationService::Init() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr obs = services::GetObserverService(); if (NS_WARN_IF(!obs)) { return false; } nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = obs->AddObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC, false); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = obs->AddObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC, false); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = obs->AddObserver(this, PRESENTATION_TERMINATE_REQUEST_TOPIC, false); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } rv = obs->AddObserver(this, PRESENTATION_RECONNECT_REQUEST_TOPIC, false); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } return !NS_WARN_IF(NS_FAILED(rv)); } NS_IMETHODIMP PresentationService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { HandleShutdown(); return NS_OK; } if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) { // Ignore the "update" case here, since we only care about the arrival and // removal of the device. if (!NS_strcmp(aData, u"add")) { nsCOMPtr device = do_QueryInterface(aSubject); if (NS_WARN_IF(!device)) { return NS_ERROR_FAILURE; } HandleDeviceAdded(device); return NS_OK; } if (!NS_strcmp(aData, u"remove")) { return HandleDeviceRemoved(); } return NS_OK; } if (!strcmp(aTopic, PRESENTATION_SESSION_REQUEST_TOPIC)) { nsCOMPtr request( do_QueryInterface(aSubject)); if (NS_WARN_IF(!request)) { return NS_ERROR_FAILURE; } return HandleSessionRequest(request); } if (!strcmp(aTopic, PRESENTATION_TERMINATE_REQUEST_TOPIC)) { nsCOMPtr request( do_QueryInterface(aSubject)); if (NS_WARN_IF(!request)) { return NS_ERROR_FAILURE; } return HandleTerminateRequest(request); } if (!strcmp(aTopic, PRESENTATION_RECONNECT_REQUEST_TOPIC)) { nsCOMPtr request( do_QueryInterface(aSubject)); if (NS_WARN_IF(!request)) { return NS_ERROR_FAILURE; } return HandleReconnectRequest(request); } if (!strcmp(aTopic, "profile-after-change")) { // It's expected since we add and entry to |kLayoutCategories| in // |nsLayoutModule.cpp| to launch this service earlier. return NS_OK; } MOZ_ASSERT(false, "Unexpected topic for PresentationService"); return NS_ERROR_UNEXPECTED; } void PresentationService::HandleShutdown() { MOZ_ASSERT(NS_IsMainThread()); Shutdown(); mAvailabilityManager.Clear(); mSessionInfoAtController.Clear(); mSessionInfoAtReceiver.Clear(); nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC); obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC); obs->RemoveObserver(this, PRESENTATION_TERMINATE_REQUEST_TOPIC); obs->RemoveObserver(this, PRESENTATION_RECONNECT_REQUEST_TOPIC); } } void PresentationService::HandleDeviceAdded(nsIPresentationDevice* aDevice) { PRES_DEBUG("%s\n", __func__); MOZ_ASSERT(aDevice); // Query for only unavailable URLs while device added. nsTArray unavailableUrls; mAvailabilityManager.GetAvailbilityUrlByAvailability(unavailableUrls, false); nsTArray supportedAvailabilityUrl; for (const auto& url : unavailableUrls) { bool isSupported; if (NS_SUCCEEDED(aDevice->IsRequestedUrlSupported(url, &isSupported)) && isSupported) { supportedAvailabilityUrl.AppendElement(url); } } if (!supportedAvailabilityUrl.IsEmpty()) { mAvailabilityManager.DoNotifyAvailableChange(supportedAvailabilityUrl, true); } } nsresult PresentationService::HandleDeviceRemoved() { PRES_DEBUG("%s\n", __func__); // Query for only available URLs while device removed. nsTArray availabilityUrls; mAvailabilityManager.GetAvailbilityUrlByAvailability(availabilityUrls, true); return UpdateAvailabilityUrlChange(availabilityUrls); } nsresult PresentationService::UpdateAvailabilityUrlChange( const nsTArray& aAvailabilityUrls) { nsCOMPtr deviceManager = do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); if (NS_WARN_IF(!deviceManager)) { return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr devices; nsresult rv = deviceManager->GetAvailableDevices(nullptr, getter_AddRefs(devices)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } uint32_t numOfDevices; devices->GetLength(&numOfDevices); nsTArray supportedAvailabilityUrl; for (const auto& url : aAvailabilityUrls) { for (uint32_t i = 0; i < numOfDevices; ++i) { nsCOMPtr device = do_QueryElementAt(devices, i); if (device) { bool isSupported; if (NS_SUCCEEDED(device->IsRequestedUrlSupported(url, &isSupported)) && isSupported) { supportedAvailabilityUrl.AppendElement(url); break; } } } } if (supportedAvailabilityUrl.IsEmpty()) { mAvailabilityManager.DoNotifyAvailableChange(aAvailabilityUrls, false); } else { mAvailabilityManager.DoNotifyAvailableChange(supportedAvailabilityUrl, true); } return NS_OK; } nsresult PresentationService::HandleSessionRequest( nsIPresentationSessionRequest* aRequest) { nsCOMPtr ctrlChannel; nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel)); if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) { return rv; } nsAutoString url; rv = aRequest->GetUrl(url); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } nsAutoString sessionId; rv = aRequest->GetPresentationId(sessionId); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } nsCOMPtr device; rv = aRequest->GetDevice(getter_AddRefs(device)); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } // Create or reuse session info. RefPtr info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER); // This is the case for reconnecting a session. // Update the control channel and device of the session info. // Call |NotifyResponderReady| to indicate the receiver page is already there. if (info) { PRES_DEBUG("handle reconnection:id[%s]\n", NS_ConvertUTF16toUTF8(sessionId).get()); info->SetControlChannel(ctrlChannel); info->SetDevice(device); return static_cast(info.get())->DoReconnect(); } // This is the case for a new session. PRES_DEBUG("handle new session:url[%s], id[%s]\n", NS_ConvertUTF16toUTF8(url).get(), NS_ConvertUTF16toUTF8(sessionId).get()); info = new PresentationPresentingInfo(url, sessionId, device); rv = info->Init(ctrlChannel); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } mSessionInfoAtReceiver.Put(sessionId, RefPtr{info}); // Notify the receiver to launch. nsCOMPtr glue = do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID); if (NS_WARN_IF(!glue)) { ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR); return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR); } RefPtr promise; rv = glue->SendRequest(url, sessionId, device, getter_AddRefs(promise)); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR); } static_cast(info.get())->SetPromise(promise); return NS_OK; } nsresult PresentationService::HandleTerminateRequest( nsIPresentationTerminateRequest* aRequest) { nsCOMPtr ctrlChannel; nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel)); if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) { return rv; } nsAutoString sessionId; rv = aRequest->GetPresentationId(sessionId); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } nsCOMPtr device; rv = aRequest->GetDevice(getter_AddRefs(device)); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } bool isFromReceiver; rv = aRequest->GetIsFromReceiver(&isFromReceiver); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } RefPtr info; if (!isFromReceiver) { info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER); } else { info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_CONTROLLER); } if (NS_WARN_IF(!info)) { // Cannot terminate non-existed session. ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR); return NS_ERROR_DOM_ABORT_ERR; } // Check if terminate request comes from known device. RefPtr knownDevice = info->GetDevice(); if (NS_WARN_IF(!IsSameDevice(device, knownDevice))) { ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR); return NS_ERROR_DOM_ABORT_ERR; } PRES_DEBUG("%s:handle termination:id[%s], receiver[%d]\n", __func__, NS_ConvertUTF16toUTF8(sessionId).get(), isFromReceiver); info->OnTerminate(ctrlChannel); return NS_OK; } nsresult PresentationService::HandleReconnectRequest( nsIPresentationSessionRequest* aRequest) { nsCOMPtr ctrlChannel; nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel)); if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) { return rv; } nsAutoString sessionId; rv = aRequest->GetPresentationId(sessionId); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } uint64_t windowId; rv = GetWindowIdBySessionIdInternal( sessionId, nsIPresentationService::ROLE_RECEIVER, &windowId); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } RefPtr info = GetSessionInfo(sessionId, nsIPresentationService::ROLE_RECEIVER); if (NS_WARN_IF(!info)) { // Cannot reconnect non-existed session ctrlChannel->Disconnect(NS_ERROR_DOM_OPERATION_ERR); return NS_ERROR_DOM_ABORT_ERR; } nsAutoString url; rv = aRequest->GetUrl(url); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Disconnect(rv); return rv; } // Make sure the url is the same as the previous one. if (NS_WARN_IF(!info->GetUrl().Equals(url))) { ctrlChannel->Disconnect(rv); return rv; } return HandleSessionRequest(aRequest); } NS_IMETHODIMP PresentationService::StartSession( const nsTArray& aUrls, const nsAString& aSessionId, const nsAString& aOrigin, const nsAString& aDeviceId, uint64_t aWindowId, EventTarget* aEventTarget, nsIPrincipal* aPrincipal, nsIPresentationServiceCallback* aCallback, nsIPresentationTransportBuilderConstructor* aBuilderConstructor) { PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aCallback); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(!aUrls.IsEmpty()); nsCOMPtr request = new PresentationDeviceRequest(aUrls, aSessionId, aOrigin, aWindowId, aEventTarget, aPrincipal, aCallback, aBuilderConstructor); if (aDeviceId.IsVoid()) { // Pop up a prompt and ask user to select a device. nsCOMPtr prompt = do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID); if (NS_WARN_IF(!prompt)) { return aCallback->NotifyError(NS_ERROR_DOM_INVALID_ACCESS_ERR); } nsresult rv = prompt->PromptDeviceSelection(request); if (NS_WARN_IF(NS_FAILED(rv))) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } return NS_OK; } // Find the designated device from available device list. nsCOMPtr deviceManager = do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); if (NS_WARN_IF(!deviceManager)) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } nsCOMPtr presentationUrls; if (NS_WARN_IF(NS_FAILED( ConvertURLArrayHelper(aUrls, getter_AddRefs(presentationUrls))))) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } nsCOMPtr devices; nsresult rv = deviceManager->GetAvailableDevices(presentationUrls, getter_AddRefs(devices)); if (NS_WARN_IF(NS_FAILED(rv))) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } nsCOMPtr enumerator; rv = devices->Enumerate(getter_AddRefs(enumerator)); if (NS_WARN_IF(NS_FAILED(rv))) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } NS_ConvertUTF16toUTF8 utf8DeviceId(aDeviceId); bool hasMore; while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr isupports; rv = enumerator->GetNext(getter_AddRefs(isupports)); nsCOMPtr device(do_QueryInterface(isupports)); MOZ_ASSERT(device); nsAutoCString id; if (NS_SUCCEEDED(device->GetId(id)) && id.Equals(utf8DeviceId)) { request->Select(device); return NS_OK; } } // Reject if designated device is not available. return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); } already_AddRefed PresentationService::CreateControllingSessionInfo(const nsAString& aUrl, const nsAString& aSessionId, uint64_t aWindowId) { MOZ_ASSERT(NS_IsMainThread()); if (aSessionId.IsEmpty()) { return nullptr; } RefPtr info = new PresentationControllingInfo(aUrl, aSessionId); mSessionInfoAtController.Put(aSessionId, RefPtr{info}); AddRespondingSessionId(aWindowId, aSessionId, nsIPresentationService::ROLE_CONTROLLER); return info.forget(); } NS_IMETHODIMP PresentationService::SendSessionMessage(const nsAString& aSessionId, uint8_t aRole, const nsAString& aData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aData.IsEmpty()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->Send(aData); } NS_IMETHODIMP PresentationService::SendSessionBinaryMsg(const nsAString& aSessionId, uint8_t aRole, const nsACString& aData) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aData.IsEmpty()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->SendBinaryMsg(aData); } NS_IMETHODIMP PresentationService::SendSessionBlob(const nsAString& aSessionId, uint8_t aRole, Blob* aBlob) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); MOZ_ASSERT(aBlob); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->SendBlob(aBlob); } NS_IMETHODIMP PresentationService::CloseSession(const nsAString& aSessionId, uint8_t aRole, uint8_t aClosedReason) { PRES_DEBUG("%s:id[%s], reason[%x], role[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), aClosedReason, aRole); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } if (aClosedReason == nsIPresentationService::CLOSED_REASON_WENTAWAY) { // Remove nsIPresentationSessionListener since we don't want to dispatch // PresentationConnectionCloseEvent if the page is went away. info->SetListener(nullptr); } return info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED); } NS_IMETHODIMP PresentationService::TerminateSession(const nsAString& aSessionId, uint8_t aRole) { PRES_DEBUG("%s:id[%s], role[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), aRole); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED); } NS_IMETHODIMP PresentationService::ReconnectSession( const nsTArray& aUrls, const nsAString& aSessionId, uint8_t aRole, nsIPresentationServiceCallback* aCallback) { PRES_DEBUG("%s:id[%s]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get()); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aCallback); MOZ_ASSERT(!aUrls.IsEmpty()); if (aRole != nsIPresentationService::ROLE_CONTROLLER) { MOZ_ASSERT(false, "Only controller can call ReconnectSession."); return NS_ERROR_INVALID_ARG; } if (NS_WARN_IF(!aCallback)) { return NS_ERROR_INVALID_ARG; } RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); } if (NS_WARN_IF(!aUrls.Contains(info->GetUrl()))) { return aCallback->NotifyError(NS_ERROR_DOM_NOT_FOUND_ERR); } return static_cast(info.get()) ->Reconnect(aCallback); } NS_IMETHODIMP PresentationService::BuildTransport(const nsAString& aSessionId, uint8_t aRole) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); if (aRole != nsIPresentationService::ROLE_CONTROLLER) { MOZ_ASSERT(false, "Only controller can call BuildTransport."); return NS_ERROR_INVALID_ARG; } RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return static_cast(info.get()) ->BuildTransport(); } NS_IMETHODIMP PresentationService::RegisterAvailabilityListener( const nsTArray& aAvailabilityUrls, nsIPresentationAvailabilityListener* aListener) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aAvailabilityUrls.IsEmpty()); MOZ_ASSERT(aListener); mAvailabilityManager.AddAvailabilityListener(aAvailabilityUrls, aListener); return UpdateAvailabilityUrlChange(aAvailabilityUrls); } NS_IMETHODIMP PresentationService::UnregisterAvailabilityListener( const nsTArray& aAvailabilityUrls, nsIPresentationAvailabilityListener* aListener) { MOZ_ASSERT(NS_IsMainThread()); mAvailabilityManager.RemoveAvailabilityListener(aAvailabilityUrls, aListener); return NS_OK; } NS_IMETHODIMP PresentationService::RegisterSessionListener( const nsAString& aSessionId, uint8_t aRole, nsIPresentationSessionListener* aListener) { PRES_DEBUG("%s:id[%s], role[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), aRole); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { // Notify the listener of TERMINATED since no correspondent session info is // available possibly due to establishment failure. This would be useful at // the receiver side, since a presentation session is created at beginning // and here is the place to realize the underlying establishment fails. nsresult rv = aListener->NotifyStateChange( aSessionId, nsIPresentationSessionListener::STATE_TERMINATED, NS_ERROR_NOT_AVAILABLE); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_ERROR_NOT_AVAILABLE; } return info->SetListener(aListener); } NS_IMETHODIMP PresentationService::UnregisterSessionListener(const nsAString& aSessionId, uint8_t aRole) { PRES_DEBUG("%s:id[%s], role[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), aRole); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (info) { // When content side decide not handling this session anymore, simply // close the connection. Session info is kept for reconnection. Unused << NS_WARN_IF(NS_FAILED( info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED))); return info->SetListener(nullptr); } return NS_OK; } NS_IMETHODIMP PresentationService::RegisterRespondingListener( uint64_t aWindowId, nsIPresentationRespondingListener* aListener) { PRES_DEBUG("%s:windowId[%" PRIu64 "]\n", __func__, aWindowId); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); nsCOMPtr listener; if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) { return (listener == aListener) ? NS_OK : NS_ERROR_DOM_INVALID_STATE_ERR; } nsTArray sessionIdArray; nsresult rv = mReceiverSessionIdManager.GetSessionIds(aWindowId, sessionIdArray); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } for (const auto& id : sessionIdArray) { aListener->NotifySessionConnect(aWindowId, id); } mRespondingListeners.Put(aWindowId, RefPtr{aListener}); return NS_OK; } NS_IMETHODIMP PresentationService::UnregisterRespondingListener(uint64_t aWindowId) { PRES_DEBUG("%s:windowId[%" PRIu64 "]\n", __func__, aWindowId); MOZ_ASSERT(NS_IsMainThread()); mRespondingListeners.Remove(aWindowId); return NS_OK; } NS_IMETHODIMP PresentationService::NotifyReceiverReady( const nsAString& aSessionId, uint64_t aWindowId, bool aIsLoading, nsIPresentationTransportBuilderConstructor* aBuilderConstructor) { PRES_DEBUG("%s:id[%s], windowId[%" PRIu64 "], loading[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), aWindowId, aIsLoading); RefPtr info = GetSessionInfo(aSessionId, nsIPresentationService::ROLE_RECEIVER); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } AddRespondingSessionId(aWindowId, aSessionId, nsIPresentationService::ROLE_RECEIVER); if (!aIsLoading) { return static_cast(info.get()) ->NotifyResponderFailure(); } nsCOMPtr listener; if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) { nsresult rv = listener->NotifySessionConnect(aWindowId, aSessionId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } info->SetTransportBuilderConstructor(aBuilderConstructor); return static_cast(info.get()) ->NotifyResponderReady(); } nsresult PresentationService::NotifyTransportClosed(const nsAString& aSessionId, uint8_t aRole, nsresult aReason) { PRES_DEBUG("%s:id[%s], reason[%" PRIx32 "], role[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), static_cast(aReason), aRole); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->NotifyTransportClosed(aReason); } NS_IMETHODIMP PresentationService::UntrackSessionInfo(const nsAString& aSessionId, uint8_t aRole) { PRES_DEBUG("%s:id[%s], role[%d]\n", __func__, NS_ConvertUTF16toUTF8(aSessionId).get(), aRole); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); // Remove the session info. if (nsIPresentationService::ROLE_CONTROLLER == aRole) { mSessionInfoAtController.Remove(aSessionId); } else { // Terminate receiver page. uint64_t windowId; nsresult rv = GetWindowIdBySessionIdInternal(aSessionId, aRole, &windowId); if (NS_SUCCEEDED(rv)) { NS_DispatchToMainThread(NS_NewRunnableFunction( "dom::PresentationService::UntrackSessionInfo", [windowId]() -> void { PRES_DEBUG("Attempt to close window[%" PRIu64 "]\n", windowId); if (auto* window = nsGlobalWindowInner::GetInnerWindowWithId(windowId)) { window->Close(); } })); } mSessionInfoAtReceiver.Remove(aSessionId); } // Remove the in-process responding info if there's still any. RemoveRespondingSessionId(aSessionId, aRole); return NS_OK; } NS_IMETHODIMP PresentationService::GetWindowIdBySessionId(const nsAString& aSessionId, uint8_t aRole, uint64_t* aWindowId) { return GetWindowIdBySessionIdInternal(aSessionId, aRole, aWindowId); } NS_IMETHODIMP PresentationService::UpdateWindowIdBySessionId(const nsAString& aSessionId, uint8_t aRole, const uint64_t aWindowId) { UpdateWindowIdBySessionIdInternal(aSessionId, aRole, aWindowId); return NS_OK; } bool PresentationService::IsSessionAccessible(const nsAString& aSessionId, const uint8_t aRole, base::ProcessId aProcessId) { MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return false; } return info->IsAccessible(aProcessId); } } // namespace dom } // namespace mozilla already_AddRefed NS_CreatePresentationService() { MOZ_ASSERT(NS_IsMainThread()); nsCOMPtr service; if (XRE_GetProcessType() == GeckoProcessType_Content) { service = new mozilla::dom::PresentationIPCService(); } else { service = new mozilla::dom::PresentationService(); if (NS_WARN_IF( !static_cast(service.get()) ->Init())) { return nullptr; } } return service.forget(); }