diff options
Diffstat (limited to 'dom/presentation/PresentationServiceBase.h')
-rw-r--r-- | dom/presentation/PresentationServiceBase.h | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/dom/presentation/PresentationServiceBase.h b/dom/presentation/PresentationServiceBase.h new file mode 100644 index 000000000..227e95430 --- /dev/null +++ b/dom/presentation/PresentationServiceBase.h @@ -0,0 +1,401 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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/. */ + +#ifndef mozilla_dom_PresentationServiceBase_h +#define mozilla_dom_PresentationServiceBase_h + +#include "mozilla/Unused.h" +#include "nsClassHashtable.h" +#include "nsCOMArray.h" +#include "nsIPresentationListener.h" +#include "nsIPresentationService.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { + +template<class T> +class PresentationServiceBase +{ +public: + PresentationServiceBase() = default; + + already_AddRefed<T> + GetSessionInfo(const nsAString& aSessionId, const uint8_t aRole) + { + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + RefPtr<T> info; + if (aRole == nsIPresentationService::ROLE_CONTROLLER) { + return mSessionInfoAtController.Get(aSessionId, getter_AddRefs(info)) ? + info.forget() : nullptr; + } else { + return mSessionInfoAtReceiver.Get(aSessionId, getter_AddRefs(info)) ? + info.forget() : nullptr; + } + } + +protected: + class SessionIdManager final + { + public: + explicit SessionIdManager() + { + MOZ_COUNT_CTOR(SessionIdManager); + } + + ~SessionIdManager() + { + MOZ_COUNT_DTOR(SessionIdManager); + } + + nsresult GetWindowId(const nsAString& aSessionId, uint64_t* aWindowId) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mRespondingWindowIds.Get(aSessionId, aWindowId)) { + return NS_OK; + } + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult GetSessionIds(uint64_t aWindowId, nsTArray<nsString>& aSessionIds) + { + MOZ_ASSERT(NS_IsMainThread()); + + nsTArray<nsString>* sessionIdArray; + if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) { + return NS_ERROR_INVALID_ARG; + } + + aSessionIds.Assign(*sessionIdArray); + return NS_OK; + } + + void AddSessionId(uint64_t aWindowId, const nsAString& aSessionId) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(aWindowId == 0)) { + return; + } + + nsTArray<nsString>* sessionIdArray; + if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) { + sessionIdArray = new nsTArray<nsString>(); + mRespondingSessionIds.Put(aWindowId, sessionIdArray); + } + + sessionIdArray->AppendElement(nsString(aSessionId)); + mRespondingWindowIds.Put(aSessionId, aWindowId); + } + + void RemoveSessionId(const nsAString& aSessionId) + { + MOZ_ASSERT(NS_IsMainThread()); + + uint64_t windowId = 0; + if (mRespondingWindowIds.Get(aSessionId, &windowId)) { + mRespondingWindowIds.Remove(aSessionId); + nsTArray<nsString>* sessionIdArray; + if (mRespondingSessionIds.Get(windowId, &sessionIdArray)) { + sessionIdArray->RemoveElement(nsString(aSessionId)); + if (sessionIdArray->IsEmpty()) { + mRespondingSessionIds.Remove(windowId); + } + } + } + } + + nsresult UpdateWindowId(const nsAString& aSessionId, const uint64_t aWindowId) + { + MOZ_ASSERT(NS_IsMainThread()); + + RemoveSessionId(aSessionId); + AddSessionId(aWindowId, aSessionId); + return NS_OK; + } + + void Clear() + { + mRespondingSessionIds.Clear(); + mRespondingWindowIds.Clear(); + } + + private: + nsClassHashtable<nsUint64HashKey, nsTArray<nsString>> mRespondingSessionIds; + nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds; + }; + + class AvailabilityManager final + { + public: + explicit AvailabilityManager() + { + MOZ_COUNT_CTOR(AvailabilityManager); + } + + ~AvailabilityManager() + { + MOZ_COUNT_DTOR(AvailabilityManager); + } + + void AddAvailabilityListener( + const nsTArray<nsString>& aAvailabilityUrls, + nsIPresentationAvailabilityListener* aListener) + { + nsTArray<nsString> dummy; + AddAvailabilityListener(aAvailabilityUrls, aListener, dummy); + } + + void AddAvailabilityListener( + const nsTArray<nsString>& aAvailabilityUrls, + nsIPresentationAvailabilityListener* aListener, + nsTArray<nsString>& aAddedUrls) + { + if (!aListener) { + MOZ_ASSERT(false, "aListener should not be null."); + return; + } + + if (aAvailabilityUrls.IsEmpty()) { + MOZ_ASSERT(false, "aAvailabilityUrls should not be empty."); + return; + } + + aAddedUrls.Clear(); + nsTArray<nsString> knownAvailableUrls; + for (const auto& url : aAvailabilityUrls) { + AvailabilityEntry* entry; + if (!mAvailabilityUrlTable.Get(url, &entry)) { + entry = new AvailabilityEntry(); + mAvailabilityUrlTable.Put(url, entry); + aAddedUrls.AppendElement(url); + } + if (!entry->mListeners.Contains(aListener)) { + entry->mListeners.AppendElement(aListener); + } + if (entry->mAvailable) { + knownAvailableUrls.AppendElement(url); + } + } + + if (!knownAvailableUrls.IsEmpty()) { + Unused << + NS_WARN_IF( + NS_FAILED(aListener->NotifyAvailableChange(knownAvailableUrls, + true))); + } else { + // If we can't find any known available url and there is no newly + // added url, we still need to notify the listener of the result. + // So, the promise returned by |getAvailability| can be resolved. + if (aAddedUrls.IsEmpty()) { + Unused << + NS_WARN_IF( + NS_FAILED(aListener->NotifyAvailableChange(aAvailabilityUrls, + false))); + } + } + } + + void RemoveAvailabilityListener( + const nsTArray<nsString>& aAvailabilityUrls, + nsIPresentationAvailabilityListener* aListener) + { + nsTArray<nsString> dummy; + RemoveAvailabilityListener(aAvailabilityUrls, aListener, dummy); + } + + void RemoveAvailabilityListener( + const nsTArray<nsString>& aAvailabilityUrls, + nsIPresentationAvailabilityListener* aListener, + nsTArray<nsString>& aRemovedUrls) + { + if (!aListener) { + MOZ_ASSERT(false, "aListener should not be null."); + return; + } + + if (aAvailabilityUrls.IsEmpty()) { + MOZ_ASSERT(false, "aAvailabilityUrls should not be empty."); + return; + } + + aRemovedUrls.Clear(); + for (const auto& url : aAvailabilityUrls) { + AvailabilityEntry* entry; + if (mAvailabilityUrlTable.Get(url, &entry)) { + entry->mListeners.RemoveElement(aListener); + if (entry->mListeners.IsEmpty()) { + mAvailabilityUrlTable.Remove(url); + aRemovedUrls.AppendElement(url); + } + } + } + } + + nsresult DoNotifyAvailableChange(const nsTArray<nsString>& aAvailabilityUrls, + bool aAvailable) + { + typedef nsClassHashtable<nsISupportsHashKey, + nsTArray<nsString>> ListenerToUrlsMap; + ListenerToUrlsMap availabilityListenerTable; + // Create a mapping from nsIPresentationAvailabilityListener to + // availabilityUrls. + for (auto it = mAvailabilityUrlTable.ConstIter(); !it.Done(); it.Next()) { + if (aAvailabilityUrls.Contains(it.Key())) { + AvailabilityEntry* entry = it.UserData(); + entry->mAvailable = aAvailable; + + for (uint32_t i = 0; i < entry->mListeners.Length(); ++i) { + nsIPresentationAvailabilityListener* listener = + entry->mListeners.ObjectAt(i); + nsTArray<nsString>* urlArray; + if (!availabilityListenerTable.Get(listener, &urlArray)) { + urlArray = new nsTArray<nsString>(); + availabilityListenerTable.Put(listener, urlArray); + } + urlArray->AppendElement(it.Key()); + } + } + } + + for (auto it = availabilityListenerTable.Iter(); !it.Done(); it.Next()) { + auto listener = + static_cast<nsIPresentationAvailabilityListener*>(it.Key()); + + Unused << + NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(*it.UserData(), + aAvailable))); + } + return NS_OK; + } + + void GetAvailbilityUrlByAvailability(nsTArray<nsString>& aOutArray, + bool aAvailable) + { + aOutArray.Clear(); + + for (auto it = mAvailabilityUrlTable.ConstIter(); !it.Done(); it.Next()) { + if (it.UserData()->mAvailable == aAvailable) { + aOutArray.AppendElement(it.Key()); + } + } + } + + void Clear() + { + mAvailabilityUrlTable.Clear(); + } + + private: + struct AvailabilityEntry + { + explicit AvailabilityEntry() + : mAvailable(false) + {} + + bool mAvailable; + nsCOMArray<nsIPresentationAvailabilityListener> mListeners; + }; + + nsClassHashtable<nsStringHashKey, AvailabilityEntry> mAvailabilityUrlTable; + }; + + virtual ~PresentationServiceBase() = default; + + void Shutdown() + { + mRespondingListeners.Clear(); + mControllerSessionIdManager.Clear(); + mReceiverSessionIdManager.Clear(); + } + + nsresult GetWindowIdBySessionIdInternal(const nsAString& aSessionId, + uint8_t aRole, + uint64_t* aWindowId) + { + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + if (NS_WARN_IF(!aWindowId)) { + return NS_ERROR_INVALID_POINTER; + } + + if (aRole == nsIPresentationService::ROLE_CONTROLLER) { + return mControllerSessionIdManager.GetWindowId(aSessionId, aWindowId); + } + + return mReceiverSessionIdManager.GetWindowId(aSessionId, aWindowId); + } + + void AddRespondingSessionId(uint64_t aWindowId, + const nsAString& aSessionId, + uint8_t aRole) + { + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + if (aRole == nsIPresentationService::ROLE_CONTROLLER) { + mControllerSessionIdManager.AddSessionId(aWindowId, aSessionId); + } else { + mReceiverSessionIdManager.AddSessionId(aWindowId, aSessionId); + } + } + + void RemoveRespondingSessionId(const nsAString& aSessionId, + uint8_t aRole) + { + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + if (aRole == nsIPresentationService::ROLE_CONTROLLER) { + mControllerSessionIdManager.RemoveSessionId(aSessionId); + } else { + mReceiverSessionIdManager.RemoveSessionId(aSessionId); + } + } + + nsresult UpdateWindowIdBySessionIdInternal(const nsAString& aSessionId, + uint8_t aRole, + const uint64_t aWindowId) + { + MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || + aRole == nsIPresentationService::ROLE_RECEIVER); + + if (aRole == nsIPresentationService::ROLE_CONTROLLER) { + return mControllerSessionIdManager.UpdateWindowId(aSessionId, aWindowId); + } + + return mReceiverSessionIdManager.UpdateWindowId(aSessionId, aWindowId); + } + + // Store the responding listener based on the window ID of the (in-process or + // OOP) receiver page. + nsRefPtrHashtable<nsUint64HashKey, nsIPresentationRespondingListener> + mRespondingListeners; + + // Store the mapping between the window ID of the in-process and OOP page and the ID + // of the responding session. It's used for both controller and receiver page + // to retrieve the correspondent session ID. Besides, also keep the mapping + // between the responding session ID and the window ID to help look up the + // window ID. + SessionIdManager mControllerSessionIdManager; + SessionIdManager mReceiverSessionIdManager; + + nsRefPtrHashtable<nsStringHashKey, T> mSessionInfoAtController; + nsRefPtrHashtable<nsStringHashKey, T> mSessionInfoAtReceiver; + + AvailabilityManager mAvailabilityManager; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationServiceBase_h |