/* -*- 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/. */ #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" #include "nsDataHashtable.h" #include "nsThread.h" namespace mozilla { namespace dom { template class PresentationServiceBase { public: PresentationServiceBase() = default; already_AddRefed GetSessionInfo(const nsAString& aSessionId, const uint8_t aRole) { MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER || aRole == nsIPresentationService::ROLE_RECEIVER); RefPtr 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); } MOZ_COUNTED_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& aSessionIds) { MOZ_ASSERT(NS_IsMainThread()); nsTArray* 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* sessionIdArray; if (!mRespondingSessionIds.Get(aWindowId, &sessionIdArray)) { sessionIdArray = new nsTArray(); 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* sessionIdArray; if (mRespondingSessionIds.Get(windowId, &sessionIdArray)) { sessionIdArray->RemoveElement(nsString(aSessionId)); if (sessionIdArray->IsEmpty()) { mRespondingSessionIds.Remove(windowId); } } } } void UpdateWindowId(const nsAString& aSessionId, const uint64_t aWindowId) { MOZ_ASSERT(NS_IsMainThread()); RemoveSessionId(aSessionId); AddSessionId(aWindowId, aSessionId); } void Clear() { mRespondingSessionIds.Clear(); mRespondingWindowIds.Clear(); } private: nsClassHashtable> mRespondingSessionIds; nsDataHashtable mRespondingWindowIds; }; class AvailabilityManager final { public: explicit AvailabilityManager() { MOZ_COUNT_CTOR(AvailabilityManager); } MOZ_COUNTED_DTOR(AvailabilityManager) void AddAvailabilityListener( const nsTArray& aAvailabilityUrls, nsIPresentationAvailabilityListener* aListener) { nsTArray dummy; AddAvailabilityListener(aAvailabilityUrls, aListener, dummy); } void AddAvailabilityListener(const nsTArray& aAvailabilityUrls, nsIPresentationAvailabilityListener* aListener, nsTArray& 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 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& aAvailabilityUrls, nsIPresentationAvailabilityListener* aListener) { nsTArray dummy; RemoveAvailabilityListener(aAvailabilityUrls, aListener, dummy); } void RemoveAvailabilityListener( const nsTArray& aAvailabilityUrls, nsIPresentationAvailabilityListener* aListener, nsTArray& 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); } } } } void DoNotifyAvailableChange(const nsTArray& aAvailabilityUrls, bool aAvailable) { typedef nsClassHashtable> 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* urlArray; if (!availabilityListenerTable.Get(listener, &urlArray)) { urlArray = new nsTArray(); availabilityListenerTable.Put(listener, urlArray); } urlArray->AppendElement(it.Key()); } } } for (auto it = availabilityListenerTable.Iter(); !it.Done(); it.Next()) { auto listener = static_cast(it.Key()); Unused << NS_WARN_IF(NS_FAILED( listener->NotifyAvailableChange(*it.UserData(), aAvailable))); } } void GetAvailbilityUrlByAvailability(nsTArray& 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 mListeners; }; nsClassHashtable 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); } } void 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) { mControllerSessionIdManager.UpdateWindowId(aSessionId, aWindowId); } else { mReceiverSessionIdManager.UpdateWindowId(aSessionId, aWindowId); } } // Store the responding listener based on the window ID of the (in-process or // OOP) receiver page. nsRefPtrHashtable 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 mSessionInfoAtController; nsRefPtrHashtable mSessionInfoAtReceiver; AvailabilityManager mAvailabilityManager; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_PresentationServiceBase_h