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([windowId]() -> void { PRES_DEBUG("Attempt to close window[%d]\n", windowId); if (auto* window = nsGlobalWindow::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 PresentationReceiver::NotifySessionConnect(uint64_t aWindowId, const nsAString& aSessionId) { PRES_DEBUG("receiver session connect:id[%s], windowId[%x]\n", NS_ConvertUTF16toUTF8(aSessionId).get(), aWindowId); if (NS_WARN_IF(!mOwner)) { return NS_ERROR_FAILURE; } if (NS_WARN_IF(aWindowId != mWindowId)) { return NS_ERROR_INVALID_ARG; } if (NS_WARN_IF(!mConnectionList)) { return NS_ERROR_FAILURE; } RefPtr<PresentationConnection> connection = PresentationConnection::Create(mOwner, aSessionId, mUrl, nsIPresentationService::ROLE_RECEIVER, mConnectionList); if (NS_WARN_IF(!connection)) { return NS_ERROR_NOT_AVAILABLE; } 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<PresentationSessionInfo> 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::ReconnectSession(const nsTArray<nsString>& 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<PresentationSessionInfo> 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<PresentationControllingInfo*>(info.get())->Reconnect(aCallback); }
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<PresentationSessionInfo> 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); }
nsresult PresentationService::HandleDeviceAdded(nsIPresentationDevice* aDevice) { PRES_DEBUG("%s\n", __func__); if (!aDevice) { MOZ_ASSERT(false, "aDevice shoud no be null."); return NS_ERROR_INVALID_ARG; } // Query for only unavailable URLs while device added. nsTArray<nsString> unavailableUrls; mAvailabilityManager.GetAvailbilityUrlByAvailability(unavailableUrls, false); nsTArray<nsString> supportedAvailabilityUrl; for (const auto& url : unavailableUrls) { bool isSupported; if (NS_SUCCEEDED(aDevice->IsRequestedUrlSupported(url, &isSupported)) && isSupported) { supportedAvailabilityUrl.AppendElement(url); } } if (!supportedAvailabilityUrl.IsEmpty()) { return mAvailabilityManager.DoNotifyAvailableChange(supportedAvailabilityUrl, true); } return NS_OK; }
NS_IMETHODIMP PresentationService::RegisterRespondingListener( uint64_t aWindowId, nsIPresentationRespondingListener* aListener) { PRES_DEBUG("%s:windowId[%lld]\n", __func__, aWindowId); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); nsCOMPtr<nsIPresentationRespondingListener> listener; if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) { return (listener == aListener) ? NS_OK : NS_ERROR_DOM_INVALID_STATE_ERR; } nsTArray<nsString> 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, aListener); 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<PresentationSessionInfo> 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<PresentationPresentingInfo*>(info.get()) ->NotifyResponderFailure(); } nsCOMPtr<nsIPresentationRespondingListener> 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<PresentationPresentingInfo*>(info.get()) ->NotifyResponderReady(); }
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; }
nsresult PresentationService::HandleDeviceRemoved() { PRES_DEBUG("%s\n", __func__); // Query for only available URLs while device removed. nsTArray<nsString> availabilityUrls; mAvailabilityManager.GetAvailbilityUrlByAvailability(availabilityUrls, true); return UpdateAvailabilityUrlChange(availabilityUrls); }
nsresult PresentationService::HandleTerminateRequest(nsIPresentationTerminateRequest* aRequest) { nsCOMPtr<nsIPresentationControlChannel> 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<nsIPresentationDevice> 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<PresentationSessionInfo> 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<nsIPresentationDevice> 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("handle termination:id[%s], receiver[%d]\n", __func__, sessionId.get(), isFromReceiver); return info->OnTerminate(ctrlChannel); }
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<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->Close(NS_OK, nsIPresentationSessionListener::STATE_TERMINATED); }
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<uint32_t>(aReason), aRole); MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } return info->NotifyTransportClosed(aReason); }
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<PresentationSessionInfo> 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::StartSession( const nsTArray<nsString>& aUrls, const nsAString& aSessionId, const nsAString& aOrigin, const nsAString& aDeviceId, uint64_t aWindowId, nsIDOMEventTarget* 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<nsIPresentationDeviceRequest> 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<nsIPresentationDevicePrompt> 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<nsIPresentationDeviceManager> deviceManager = do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); if (NS_WARN_IF(!deviceManager)) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } nsCOMPtr<nsIArray> presentationUrls; if (NS_WARN_IF(NS_FAILED( ConvertURLArrayHelper(aUrls, getter_AddRefs(presentationUrls))))) { return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR); } nsCOMPtr<nsIArray> 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<nsISimpleEnumerator> 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<nsISupports> isupports; rv = enumerator->GetNext(getter_AddRefs(isupports)); nsCOMPtr<nsIPresentationDevice> 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); }
nsresult PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aRequest) { nsCOMPtr<nsIPresentationControlChannel> 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<nsIPresentationDevice> 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<PresentationSessionInfo> 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<PresentationPresentingInfo*>( info.get())->DoReconnect(); } // This is the case for a new session. PRES_DEBUG("handle new session:url[%d], 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, info); // Notify the receiver to launch. nsCOMPtr<nsIPresentationRequestUIGlue> 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); } nsCOMPtr<nsISupports> 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); } nsCOMPtr<Promise> realPromise = do_QueryInterface(promise); static_cast<PresentationPresentingInfo*>(info.get())->SetPromise(realPromise); return NS_OK; }