Bug 1259349 - Filter the device availability by URL. r=smaug
authorKershaw Chang <kechang@mozilla.com>
Mon, 07 Nov 2016 22:13:00 -0500
changeset 348432 4975b2d475fbda30526ec997abd00f94b4fef55a
parent 348431 e6992502f673232fe03419e7cdcbeb551adfb381
child 348433 2631d596fc32ead225e077a2edbf44f19a7a8282
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1259349
milestone52.0a1
Bug 1259349 - Filter the device availability by URL. r=smaug
dom/presentation/PresentationAvailability.cpp
dom/presentation/PresentationAvailability.h
dom/presentation/PresentationService.cpp
dom/presentation/PresentationService.h
dom/presentation/PresentationServiceBase.h
dom/presentation/interfaces/nsIPresentationListener.idl
dom/presentation/interfaces/nsIPresentationService.idl
dom/presentation/ipc/PPresentation.ipdl
dom/presentation/ipc/PresentationChild.cpp
dom/presentation/ipc/PresentationChild.h
dom/presentation/ipc/PresentationIPCService.cpp
dom/presentation/ipc/PresentationIPCService.h
dom/presentation/ipc/PresentationParent.cpp
dom/presentation/ipc/PresentationParent.h
dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js
dom/presentation/tests/mochitest/test_presentation_availability.html
--- a/dom/presentation/PresentationAvailability.cpp
+++ b/dom/presentation/PresentationAvailability.cpp
@@ -48,33 +48,36 @@ PresentationAvailability::Create(nsPIDOM
 }
 
 PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow,
                                                    const nsTArray<nsString>& aUrls)
   : DOMEventTargetHelper(aWindow)
   , mIsAvailable(false)
   , mUrls(aUrls)
 {
+  for (uint32_t i = 0; i < mUrls.Length(); ++i) {
+    mAvailabilityOfUrl.AppendElement(false);
+  }
 }
 
 PresentationAvailability::~PresentationAvailability()
 {
   Shutdown();
 }
 
 bool
 PresentationAvailability::Init(RefPtr<Promise>& aPromise)
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return false;
   }
 
-  nsresult rv = service->RegisterAvailabilityListener(this);
+  nsresult rv = service->RegisterAvailabilityListener(mUrls, this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     // If the user agent is unable to monitor available device,
     // Resolve promise with |value| set to false.
     mIsAvailable = false;
     aPromise->MaybeResolve(this);
     return true;
   }
 
@@ -97,17 +100,18 @@ void PresentationAvailability::Shutdown(
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return;
   }
 
   Unused <<
-    NS_WARN_IF(NS_FAILED(service->UnregisterAvailabilityListener(this)));
+    NS_WARN_IF(NS_FAILED(service->UnregisterAvailabilityListener(mUrls,
+                                                                 this)));
 }
 
 /* virtual */ void
 PresentationAvailability::DisconnectFromOwner()
 {
   Shutdown();
   DOMEventTargetHelper::DisconnectFromOwner();
 }
@@ -152,22 +156,31 @@ PresentationAvailability::EnqueuePromise
 
 bool
 PresentationAvailability::Value() const
 {
   return mIsAvailable;
 }
 
 NS_IMETHODIMP
-PresentationAvailability::NotifyAvailableChange(bool aIsAvailable)
+PresentationAvailability::NotifyAvailableChange(const nsTArray<nsString>& aAvailabilityUrls,
+                                                bool aIsAvailable)
 {
+  bool available = false;
+  for (uint32_t i = 0; i < mUrls.Length(); ++i) {
+    if (aAvailabilityUrls.Contains(mUrls[i])) {
+      mAvailabilityOfUrl[i] = aIsAvailable;
+    }
+    available |= mAvailabilityOfUrl[i];
+  }
+
   return NS_DispatchToCurrentThread(NewRunnableMethod
                                     <bool>(this,
                                            &PresentationAvailability::UpdateAvailabilityAndDispatchEvent,
-                                           aIsAvailable));
+                                           available));
 }
 
 void
 PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
 {
   PRES_DEBUG("%s\n", __func__);
   bool isChanged = (aIsAvailable != mIsAvailable);
 
--- a/dom/presentation/PresentationAvailability.h
+++ b/dom/presentation/PresentationAvailability.h
@@ -60,14 +60,15 @@ private:
 
   void UpdateAvailabilityAndDispatchEvent(bool aIsAvailable);
 
   bool mIsAvailable;
 
   nsTArray<RefPtr<Promise>> mPromises;
 
   nsTArray<nsString> mUrls;
+  nsTArray<bool> mAvailabilityOfUrl;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationAvailability_h
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -21,19 +21,16 @@
 #include "nsISupportsPrimitives.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCID.h"
 #include "nsXULAppAPI.h"
 #include "PresentationLog.h"
 
-using namespace mozilla;
-using namespace mozilla::dom;
-
 namespace mozilla {
 namespace dom {
 
 static bool
 IsSameDevice(nsIPresentationDevice* aDevice, nsIPresentationDevice* aDeviceAnother) {
   if (!aDevice || !aDeviceAnother) {
     return false;
   }
@@ -127,19 +124,16 @@ private:
   nsWeakPtr mChromeEventHandler;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationTransportBuilderConstructor> mBuilderConstructor;
 };
 
 LazyLogModule gPresentationLog("Presentation");
 
-} // namespace dom
-} // namespace mozilla
-
 NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
 
 PresentationDeviceRequest::PresentationDeviceRequest(
                const nsTArray<nsString>& aUrls,
                const nsAString& aId,
                const nsAString& aOrigin,
                uint64_t aWindowId,
                nsIDOMEventTarget* aEventTarget,
@@ -271,17 +265,16 @@ PresentationDeviceRequest::Cancel(nsresu
  * Implementation of PresentationService
  */
 
 NS_IMPL_ISUPPORTS(PresentationService,
                   nsIPresentationService,
                   nsIObserver)
 
 PresentationService::PresentationService()
-  : mIsAvailable(false)
 {
 }
 
 PresentationService::~PresentationService()
 {
   HandleShutdown();
 }
 
@@ -311,36 +304,42 @@ PresentationService::Init()
   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;
   }
 
-  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
-    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
-  if (NS_WARN_IF(!deviceManager)) {
-    return false;
-  }
-
-  rv = deviceManager->GetDeviceAvailable(&mIsAvailable);
   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;
   } else if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) {
-    return HandleDeviceChange();
+    // Ignore the "update" case here, since we only care about the arrival and
+    // removal of the device.
+    if (!NS_strcmp(aData, u"add")) {
+      nsCOMPtr<nsIPresentationDevice> device = do_QueryInterface(aSubject);
+      if (NS_WARN_IF(!device)) {
+        return NS_ERROR_FAILURE;
+      }
+
+      return HandleDeviceAdded(device);
+    } else if(!NS_strcmp(aData, u"remove")) {
+      return HandleDeviceRemoved();
+    }
+
+    return NS_OK;
   } else if (!strcmp(aTopic, PRESENTATION_SESSION_REQUEST_TOPIC)) {
     nsCOMPtr<nsIPresentationSessionRequest> request(do_QueryInterface(aSubject));
     if (NS_WARN_IF(!request)) {
       return NS_ERROR_FAILURE;
     }
 
     return HandleSessionRequest(request);
   } else if (!strcmp(aTopic, PRESENTATION_TERMINATE_REQUEST_TOPIC)) {
@@ -369,53 +368,114 @@ PresentationService::Observe(nsISupports
 
 void
 PresentationService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   Shutdown();
 
-  mAvailabilityListeners.Clear();
+  mAvailabilityManager.Clear();
   mSessionInfoAtController.Clear();
   mSessionInfoAtReceiver.Clear();
 
   nsCOMPtr<nsIObserverService> 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);
   }
 }
 
 nsresult
-PresentationService::HandleDeviceChange()
+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;
+}
+
+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::UpdateAvailabilityUrlChange(
+                                   const nsTArray<nsString>& aAvailabilityUrls)
+{
   nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
     do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
   if (NS_WARN_IF(!deviceManager)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  bool isAvailable;
-  nsresult rv = deviceManager->GetDeviceAvailable(&isAvailable);
+  nsCOMPtr<nsIArray> devices;
+  nsresult rv = deviceManager->GetAvailableDevices(nullptr,
+                                                   getter_AddRefs(devices));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  if (isAvailable != mIsAvailable) {
-    mIsAvailable = isAvailable;
-    NotifyAvailableChange(mIsAvailable);
+  uint32_t numOfDevices;
+  devices->GetLength(&numOfDevices);
+
+  nsTArray<nsString> supportedAvailabilityUrl;
+  for (const auto& url : aAvailabilityUrls) {
+    for (uint32_t i = 0; i < numOfDevices; ++i) {
+      nsCOMPtr<nsIPresentationDevice> device = do_QueryElementAt(devices, i);
+      if (device) {
+        bool isSupported;
+        if (NS_SUCCEEDED(device->IsRequestedUrlSupported(url, &isSupported)) &&
+            isSupported) {
+          supportedAvailabilityUrl.AppendElement(url);
+          break;
+        }
+      }
+    }
   }
 
-  return NS_OK;
+  if (supportedAvailabilityUrl.IsEmpty()) {
+    return mAvailabilityManager.DoNotifyAvailableChange(aAvailabilityUrls,
+                                                        false);
+  }
+
+  return mAvailabilityManager.DoNotifyAvailableChange(supportedAvailabilityUrl,
+                                                      true);
 }
 
 nsresult
 PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aRequest)
 {
   nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
   nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel));
   if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) {
@@ -592,27 +652,16 @@ PresentationService::HandleReconnectRequ
   if (NS_WARN_IF(!info->GetUrl().Equals(url))) {
     ctrlChannel->Disconnect(rv);
     return rv;
   }
 
   return HandleSessionRequest(aRequest);
 }
 
-void
-PresentationService::NotifyAvailableChange(bool aIsAvailable)
-{
-  nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>>::ForwardIterator iter(mAvailabilityListeners);
-  while (iter.HasMore()) {
-    nsCOMPtr<nsIPresentationAvailabilityListener> listener = iter.GetNext();
-    Unused <<
-      NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aIsAvailable)));
-  }
-}
-
 NS_IMETHODIMP
 PresentationService::StartSession(
                const nsTArray<nsString>& aUrls,
                const nsAString& aSessionId,
                const nsAString& aOrigin,
                const nsAString& aDeviceId,
                uint64_t aWindowId,
                nsIDOMEventTarget* aEventTarget,
@@ -873,38 +922,36 @@ PresentationService::BuildTransport(cons
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return static_cast<PresentationControllingInfo*>(info.get())->BuildTransport();
 }
 
 NS_IMETHODIMP
-PresentationService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
+PresentationService::RegisterAvailabilityListener(
+                                const nsTArray<nsString>& 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<nsString>& aAvailabilityUrls,
+                                nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mAvailabilityListeners.Contains(aListener)) {
-    mAvailabilityListeners.AppendElement(aListener);
-  }
-
-  // Leverage availablility change notification to assign
-  // the initial value of availability object.
-  Unused <<
-    NS_WARN_IF(NS_FAILED(aListener->NotifyAvailableChange(mIsAvailable)));
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-PresentationService::UnregisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  mAvailabilityListeners.RemoveElement(aListener);
+  mAvailabilityManager.RemoveAvailabilityListener(aAvailabilityUrls, aListener);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::RegisterSessionListener(const nsAString& aSessionId,
                                              uint8_t aRole,
                                              nsIPresentationSessionListener* aListener)
 {
@@ -1114,16 +1161,19 @@ PresentationService::IsSessionAccessible
              aRole == nsIPresentationService::ROLE_RECEIVER);
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
   if (NS_WARN_IF(!info)) {
     return false;
   }
   return info->IsAccessible(aProcessId);
 }
 
+} // namespace dom
+} // namespace mozilla
+
 already_AddRefed<nsIPresentationService>
 NS_CreatePresentationService()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIPresentationService> service;
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     service = new mozilla::dom::PresentationIPCService();
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -4,17 +4,16 @@
  * 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_PresentationService_h
 #define mozilla_dom_PresentationService_h
 
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
-#include "nsTObserverArray.h"
 #include "PresentationServiceBase.h"
 #include "PresentationSessionInfo.h"
 
 class nsIPresentationSessionRequest;
 class nsIPresentationTerminateRequest;
 class nsIURI;
 class nsIPresentationSessionTransportBuilder;
 
@@ -41,28 +40,29 @@ public:
                            const uint8_t aRole,
                            base::ProcessId aProcessId);
 
 private:
   friend class PresentationDeviceRequest;
 
   virtual ~PresentationService();
   void HandleShutdown();
-  nsresult HandleDeviceChange();
+  nsresult HandleDeviceAdded(nsIPresentationDevice* aDevice);
+  nsresult HandleDeviceRemoved();
   nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
   nsresult HandleTerminateRequest(nsIPresentationTerminateRequest* aRequest);
   nsresult HandleReconnectRequest(nsIPresentationSessionRequest* aRequest);
-  void NotifyAvailableChange(bool aIsAvailable);
 
   // This is meant to be called by PresentationDeviceRequest.
   already_AddRefed<PresentationSessionInfo>
   CreateControllingSessionInfo(const nsAString& aUrl,
                                const nsAString& aSessionId,
                                uint64_t aWindowId);
 
-  bool mIsAvailable;
-  nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>> mAvailabilityListeners;
+  // Emumerate all devices to get the availability of each input Urls.
+  nsresult UpdateAvailabilityUrlChange(
+                                  const nsTArray<nsString>& aAvailabilityUrls);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
--- a/dom/presentation/PresentationServiceBase.h
+++ b/dom/presentation/PresentationServiceBase.h
@@ -2,24 +2,25 @@
 /* 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"
 
-class nsIPresentationRespondingListener;
-
 namespace mozilla {
 namespace dom {
 
 template<class T>
 class PresentationServiceBase
 {
 public:
   PresentationServiceBase() = default;
@@ -127,16 +128,191 @@ protected:
       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();
   }
@@ -210,14 +386,16 @@ protected:
   // 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
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -1,21 +1,24 @@
 /* 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 "nsISupports.idl"
 
-[scriptable, uuid(0105f837-4279-4715-9d5b-2dc3f8b65353)]
+[ref] native URLArrayRef(const nsTArray<nsString>);
+
+[uuid(0105f837-4279-4715-9d5b-2dc3f8b65353)]
 interface nsIPresentationAvailabilityListener : nsISupports
 {
   /*
    * Called when device availability changes.
    */
-  void notifyAvailableChange(in bool available);
+  [noscript] void notifyAvailableChange(in URLArrayRef urls,
+                                        in bool available);
 };
 
 [scriptable, uuid(7dd48df8-8f8c-48c7-ac37-7b9fd1acf2f8)]
 interface nsIPresentationSessionListener : nsISupports
 {
   const unsigned short STATE_CONNECTING = 0;
   const unsigned short STATE_CONNECTED = 1;
   const unsigned short STATE_CLOSED = 2;
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -153,25 +153,32 @@ interface nsIPresentationService : nsISu
   [noscript] void reconnectSession(in URLArrayRef urls,
                                    in DOMString sessionId,
                                    in uint8_t role,
                                    in nsIPresentationServiceCallback callback);
 
   /*
    * Register an availability listener. Must be called from the main thread.
    *
+   * @param availabilityUrls: The Urls that this listener is interested in.
    * @param listener: The listener to register.
    */
-  void registerAvailabilityListener(in nsIPresentationAvailabilityListener listener);
+  [noscript] void registerAvailabilityListener(
+                              in URLArrayRef availabilityUrls,
+                              in nsIPresentationAvailabilityListener listener);
 
   /*
    * Unregister an availability listener. Must be called from the main thread.
+   *
+   * @param availabilityUrls: The Urls that are registered before.
    * @param listener: The listener to unregister.
    */
-  void unregisterAvailabilityListener(in nsIPresentationAvailabilityListener listener);
+  [noscript] void unregisterAvailabilityListener(
+                              in URLArrayRef availabilityUrls,
+                              in nsIPresentationAvailabilityListener listener);
 
   /*
    * Register a session listener. Must be called from the main thread.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    * @param listener: The listener to register.
    */
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -72,33 +72,34 @@ union PresentationIPCRequest
 
 sync protocol PPresentation
 {
   manager PContent;
   manages PPresentationBuilder;
   manages PPresentationRequest;
 
 child:
-  async NotifyAvailableChange(bool aAvailable);
+  async NotifyAvailableChange(nsString[] aAvailabilityUrls,
+                              bool aAvailable);
   async NotifySessionStateChange(nsString aSessionId,
                                  uint16_t aState,
                                  nsresult aReason);
   async NotifyMessage(nsString aSessionId, nsCString aData, bool aIsBinary);
   async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId);
   async NotifyCloseSessionTransport(nsString aSessionId,
                                     uint8_t aRole,
                                     nsresult aReason);
 
   async PPresentationBuilder(nsString aSessionId, uint8_t aRole);
 
 parent:
   async __delete__();
 
-  async RegisterAvailabilityHandler();
-  async UnregisterAvailabilityHandler();
+  async RegisterAvailabilityHandler(nsString[] aAvailabilityUrls);
+  async UnregisterAvailabilityHandler(nsString[] aAvailabilityUrls);
 
   async RegisterSessionHandler(nsString aSessionId, uint8_t aRole);
   async UnregisterSessionHandler(nsString aSessionId, uint8_t aRole);
 
   async RegisterRespondingHandler(uint64_t aWindowId);
   async UnregisterRespondingHandler(uint64_t aWindowId);
 
   async PPresentationRequest(PresentationIPCRequest aRequest);
--- a/dom/presentation/ipc/PresentationChild.cpp
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -84,20 +84,24 @@ PresentationChild::DeallocPPresentationB
 {
   RefPtr<PresentationBuilderChild> actor =
     dont_AddRef(static_cast<PresentationBuilderChild*>(aActor));
   return true;
 }
 
 
 bool
-PresentationChild::RecvNotifyAvailableChange(const bool& aAvailable)
+PresentationChild::RecvNotifyAvailableChange(
+                                        nsTArray<nsString>&& aAvailabilityUrls,
+                                        const bool& aAvailable)
 {
   if (mService) {
-    Unused << NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailable)));
+    Unused <<
+      NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailabilityUrls,
+                                                           aAvailable)));
   }
   return true;
 }
 
 bool
 PresentationChild::RecvNotifySessionStateChange(const nsString& aSessionId,
                                                 const uint16_t& aState,
                                                 const nsresult& aReason)
--- a/dom/presentation/ipc/PresentationChild.h
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -38,17 +38,18 @@ public:
 
   virtual PPresentationBuilderChild*
   AllocPPresentationBuilderChild(const nsString& aSessionId, const uint8_t& aRole) override;
 
   virtual bool
   DeallocPPresentationBuilderChild(PPresentationBuilderChild* aActor) override;
 
   virtual bool
-  RecvNotifyAvailableChange(const bool& aAvailable) override;
+  RecvNotifyAvailableChange(nsTArray<nsString>&& aAvailabilityUrls,
+                            const bool& aAvailable) override;
 
   virtual bool
   RecvNotifySessionStateChange(const nsString& aSessionId,
                                const uint16_t& aState,
                                const nsresult& aReason) override;
 
   virtual bool
   RecvNotifyMessage(const nsString& aSessionId,
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -23,17 +23,19 @@ using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 namespace {
 
 PresentationChild* sPresentationChild;
 
 } // anonymous
 
-NS_IMPL_ISUPPORTS(PresentationIPCService, nsIPresentationService)
+NS_IMPL_ISUPPORTS(PresentationIPCService,
+                  nsIPresentationService,
+                  nsIPresentationAvailabilityListener)
 
 PresentationIPCService::PresentationIPCService()
 {
   ContentChild* contentChild = ContentChild::GetSingleton();
   if (NS_WARN_IF(!contentChild)) {
     return;
   }
   sPresentationChild = new PresentationChild(this);
@@ -41,17 +43,16 @@ PresentationIPCService::PresentationIPCS
     NS_WARN_IF(!contentChild->SendPPresentationConstructor(sPresentationChild));
 }
 
 /* virtual */
 PresentationIPCService::~PresentationIPCService()
 {
   Shutdown();
 
-  mAvailabilityListeners.Clear();
   mSessionListeners.Clear();
   mSessionInfoAtController.Clear();
   mSessionInfoAtReceiver.Clear();
   sPresentationChild = nullptr;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(
@@ -229,39 +230,53 @@ PresentationIPCService::SendRequest(nsIP
   if (sPresentationChild) {
     PresentationRequestChild* actor = new PresentationRequestChild(aCallback);
     Unused << NS_WARN_IF(!sPresentationChild->SendPPresentationRequestConstructor(actor, aRequest));
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationIPCService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
+PresentationIPCService::RegisterAvailabilityListener(
+                                const nsTArray<nsString>& aAvailabilityUrls,
+                                nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aAvailabilityUrls.IsEmpty());
   MOZ_ASSERT(aListener);
 
-  mAvailabilityListeners.AppendElement(aListener);
-  if (sPresentationChild) {
+  nsTArray<nsString> addedUrls;
+  mAvailabilityManager.AddAvailabilityListener(aAvailabilityUrls,
+                                               aListener,
+                                               addedUrls);
+
+  if (sPresentationChild && !addedUrls.IsEmpty()) {
     Unused <<
-      NS_WARN_IF(!sPresentationChild->SendRegisterAvailabilityHandler());
+      NS_WARN_IF(
+        !sPresentationChild->SendRegisterAvailabilityHandler(addedUrls));
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationIPCService::UnregisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
+PresentationIPCService::UnregisterAvailabilityListener(
+                                const nsTArray<nsString>& aAvailabilityUrls,
+                                nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aListener);
 
-  mAvailabilityListeners.RemoveElement(aListener);
-  if (mAvailabilityListeners.IsEmpty() && sPresentationChild) {
+  nsTArray<nsString> removedUrls;
+  mAvailabilityManager.RemoveAvailabilityListener(aAvailabilityUrls,
+                                                  aListener,
+                                                  removedUrls);
+
+  if (sPresentationChild && !removedUrls.IsEmpty()) {
     Unused <<
-      NS_WARN_IF(!sPresentationChild->SendUnregisterAvailabilityHandler());
+      NS_WARN_IF(
+        !sPresentationChild->SendUnregisterAvailabilityHandler(removedUrls));
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId,
                                                 uint8_t aRole,
                                                 nsIPresentationSessionListener* aListener)
@@ -414,26 +429,23 @@ PresentationIPCService::NotifySessionCon
   nsCOMPtr<nsIPresentationRespondingListener> listener;
   if (NS_WARN_IF(!mRespondingListeners.Get(aWindowId, getter_AddRefs(listener)))) {
     return NS_OK;
   }
 
   return listener->NotifySessionConnect(aWindowId, aSessionId);
 }
 
-nsresult
-PresentationIPCService::NotifyAvailableChange(bool aAvailable)
+NS_IMETHODIMP
+PresentationIPCService::NotifyAvailableChange(
+                                   const nsTArray<nsString>& aAvailabilityUrls,
+                                   bool aAvailable)
 {
-  nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener>>::ForwardIterator iter(mAvailabilityListeners);
-  while (iter.HasMore()) {
-    nsIPresentationAvailabilityListener* listener = iter.GetNext();
-    Unused << NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aAvailable)));
-  }
-
-  return NS_OK;
+  return mAvailabilityManager.DoNotifyAvailableChange(aAvailabilityUrls,
+                                                      aAvailable);
 }
 
 NS_IMETHODIMP
 PresentationIPCService::NotifyReceiverReady(
                const nsAString& aSessionId,
                uint64_t aWindowId,
                bool aIsLoading,
                nsIPresentationTransportBuilderConstructor* aBuilderConstructor)
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -3,41 +3,41 @@
 /* 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_PresentationIPCService_h
 #define mozilla_dom_PresentationIPCService_h
 
 #include "mozilla/dom/PresentationServiceBase.h"
+#include "nsIPresentationListener.h"
 #include "nsIPresentationSessionTransport.h"
 #include "nsIPresentationService.h"
-#include "nsTObserverArray.h"
 
 class nsIDocShell;
 
 namespace mozilla {
 namespace dom {
 
 class PresentationIPCRequest;
 class PresentationContentSessionInfo;
 class PresentationResponderLoadingCallback;
 
 class PresentationIPCService final
-  : public nsIPresentationService
+  : public nsIPresentationAvailabilityListener
+  , public nsIPresentationService
   , public PresentationServiceBase<PresentationContentSessionInfo>
 {
 public:
   NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONAVAILABILITYLISTENER
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationIPCService();
 
-  nsresult NotifyAvailableChange(bool aAvailable);
-
   nsresult NotifySessionStateChange(const nsAString& aSessionId,
                                     uint16_t aState,
                                     nsresult aReason);
 
   nsresult NotifyMessage(const nsAString& aSessionId,
                          const nsACString& aData,
                          const bool& aIsBinary);
 
@@ -57,17 +57,16 @@ public:
                                         uint8_t aRole,
                                         nsresult aReason);
 
 private:
   virtual ~PresentationIPCService();
   nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
                        const PresentationIPCRequest& aRequest);
 
-  nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener> > mAvailabilityListeners;
   nsRefPtrHashtable<nsStringHashKey,
                     nsIPresentationSessionListener> mSessionListeners;
   nsRefPtrHashtable<nsUint64HashKey,
                     nsIPresentationRespondingListener> mRespondingListeners;
   RefPtr<PresentationResponderLoadingCallback> mCallback;
 };
 
 } // namespace dom
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -129,17 +129,19 @@ PresentationParent::ActorDestroy(ActorDe
   mSessionIdsAtReceiver.Clear();
 
   for (uint32_t i = 0; i < mWindowIds.Length(); i++) {
     Unused << NS_WARN_IF(NS_FAILED(mService->
       UnregisterRespondingListener(mWindowIds[i])));
   }
   mWindowIds.Clear();
 
-  mService->UnregisterAvailabilityListener(this);
+  if (!mContentAvailabilityUrls.IsEmpty()) {
+    mService->UnregisterAvailabilityListener(mContentAvailabilityUrls, this);
+  }
   mService = nullptr;
 }
 
 bool
 PresentationParent::RecvPPresentationRequestConstructor(
   PPresentationRequestParent* aActor,
   const PresentationIPCRequest& aRequest)
 {
@@ -207,28 +209,40 @@ PresentationParent::DeallocPPresentation
 
 bool
 PresentationParent::Recv__delete__()
 {
   return true;
 }
 
 bool
-PresentationParent::RecvRegisterAvailabilityHandler()
+PresentationParent::RecvRegisterAvailabilityHandler(
+                                       nsTArray<nsString>&& aAvailabilityUrls)
 {
   MOZ_ASSERT(mService);
-  Unused << NS_WARN_IF(NS_FAILED(mService->RegisterAvailabilityListener(this)));
+
+  Unused << NS_WARN_IF(NS_FAILED(mService->RegisterAvailabilityListener(
+                                                             aAvailabilityUrls,
+                                                             this)));
+  mContentAvailabilityUrls.AppendElements(aAvailabilityUrls);
   return true;
 }
 
 bool
-PresentationParent::RecvUnregisterAvailabilityHandler()
+PresentationParent::RecvUnregisterAvailabilityHandler(
+                                        nsTArray<nsString>&& aAvailabilityUrls)
 {
   MOZ_ASSERT(mService);
-  Unused << NS_WARN_IF(NS_FAILED(mService->UnregisterAvailabilityListener(this)));
+
+  Unused << NS_WARN_IF(NS_FAILED(mService->UnregisterAvailabilityListener(
+                                                             aAvailabilityUrls,
+                                                             this)));
+  for (const auto& url : aAvailabilityUrls) {
+    mContentAvailabilityUrls.RemoveElement(url);
+  }
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvRegisterSessionHandler(const nsString& aSessionId,
                                                const uint8_t& aRole)
 {
   MOZ_ASSERT(mService);
@@ -278,19 +292,22 @@ PresentationParent::RecvUnregisterRespon
 {
   MOZ_ASSERT(mService);
   mWindowIds.RemoveElement(aWindowId);
   Unused << NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(aWindowId)));
   return true;
 }
 
 NS_IMETHODIMP
-PresentationParent::NotifyAvailableChange(bool aAvailable)
+PresentationParent::NotifyAvailableChange(const nsTArray<nsString>& aAvailabilityUrls,
+                                          bool aAvailable)
 {
-  if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) {
+  if (NS_WARN_IF(mActorDestroyed ||
+                 !SendNotifyAvailableChange(aAvailabilityUrls,
+                                            aAvailable))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyStateChange(const nsAString& aSessionId,
                                       uint16_t aState,
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -52,19 +52,21 @@ public:
                                   const uint8_t& aRole) override;
 
   virtual bool
   DeallocPPresentationBuilderParent(
     PPresentationBuilderParent* aActor) override;
 
   virtual bool Recv__delete__() override;
 
-  virtual bool RecvRegisterAvailabilityHandler() override;
+  virtual bool RecvRegisterAvailabilityHandler(
+                             nsTArray<nsString>&& aAvailabilityUrls) override;
 
-  virtual bool RecvUnregisterAvailabilityHandler() override;
+  virtual bool RecvUnregisterAvailabilityHandler(
+                             nsTArray<nsString>&& aAvailabilityUrls) override;
 
   virtual bool RecvRegisterSessionHandler(const nsString& aSessionId,
                                           const uint8_t& aRole) override;
 
   virtual bool RecvUnregisterSessionHandler(const nsString& aSessionId,
                                             const uint8_t& aRole) override;
 
   virtual bool RecvRegisterRespondingHandler(const uint64_t& aWindowId) override;
@@ -83,16 +85,17 @@ private:
   virtual ~PresentationParent();
 
   bool mActorDestroyed = false;
   nsCOMPtr<nsIPresentationService> mService;
   nsTArray<nsString> mSessionIdsAtController;
   nsTArray<nsString> mSessionIdsAtReceiver;
   nsTArray<uint64_t> mWindowIds;
   ContentParentId mChildId;
+  nsTArray<nsString> mContentAvailabilityUrls;
 };
 
 class PresentationRequestParent final : public PPresentationRequestParent
                                       , public nsIPresentationServiceCallback
 {
   friend class PresentationParent;
 
 public:
--- a/dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationDeviceInfoChromeScript.js
@@ -30,35 +30,121 @@ var testDevice = {
     return true;
   },
   id: null,
   name: null,
   type: null,
   listener: null,
 };
 
+var testDevice1 = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+  id: 'dummyid',
+  name: 'dummyName',
+  type: 'dummyType',
+  establishControlChannel: function(url, presentationId) {
+    return null;
+  },
+  disconnect: function() {},
+  isRequestedUrlSupported: function(requestedUrl) {
+    return true;
+  },
+};
+
+var testDevice2 = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+  id: 'dummyid',
+  name: 'dummyName',
+  type: 'dummyType',
+  establishControlChannel: function(url, presentationId) {
+    return null;
+  },
+  disconnect: function() {},
+  isRequestedUrlSupported: function(requestedUrl) {
+    return true;
+  },
+};
+
+var mockedDeviceWithoutSupportedURL = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+  id: 'dummyid',
+  name: 'dummyName',
+  type: 'dummyType',
+  establishControlChannel: function(url, presentationId) {
+    return null;
+  },
+  disconnect: function() {},
+  isRequestedUrlSupported: function(requestedUrl) {
+    return false;
+  },
+};
+
+var mockedDeviceSupportHttpsURL = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+  id: 'dummyid',
+  name: 'dummyName',
+  type: 'dummyType',
+  establishControlChannel: function(url, presentationId) {
+    return null;
+  },
+  disconnect: function() {},
+  isRequestedUrlSupported: function(requestedUrl) {
+    if (requestedUrl.indexOf("https://") != -1) {
+      return true;
+    }
+    return false;
+  },
+};
+
 addMessageListener('setup', function() {
   manager.addDeviceProvider(testProvider);
 
   sendAsyncMessage('setup-complete');
 });
 
 addMessageListener('trigger-device-add', function(device) {
   testDevice.id = device.id;
   testDevice.name = device.name;
   testDevice.type = device.type;
   manager.addDevice(testDevice);
 });
 
+addMessageListener('trigger-add-unsupport-url-device', function() {
+  manager.addDevice(mockedDeviceWithoutSupportedURL);
+});
+
+addMessageListener('trigger-add-multiple-devices', function() {
+  manager.addDevice(testDevice1);
+  manager.addDevice(testDevice2);
+});
+
+addMessageListener('trigger-add-https-devices', function() {
+  manager.addDevice(mockedDeviceSupportHttpsURL);
+});
+
+
 addMessageListener('trigger-device-update', function(device) {
   testDevice.id = device.id;
   testDevice.name = device.name;
   testDevice.type = device.type;
   manager.updateDevice(testDevice);
 });
 
 addMessageListener('trigger-device-remove', function() {
   manager.removeDevice(testDevice);
 });
 
+addMessageListener('trigger-remove-unsupported-device', function() {
+  manager.removeDevice(mockedDeviceWithoutSupportedURL);
+});
+
+addMessageListener('trigger-remove-multiple-devices', function() {
+  manager.removeDevice(testDevice1);
+  manager.removeDevice(testDevice2);
+});
+
+addMessageListener('trigger-remove-https-devices', function() {
+  manager.removeDevice(mockedDeviceSupportHttpsURL);
+});
+
 addMessageListener('teardown', function() {
   manager.removeDeviceProvider(testProvider);
 });
--- a/dom/presentation/tests/mochitest/test_presentation_availability.html
+++ b/dom/presentation/tests/mochitest/test_presentation_availability.html
@@ -93,35 +93,140 @@ function testConsecutiveGetAvailability(
       ok(firstAvailabilityResolved, "getAvailability() should be resolved in sequence");
     })
   ]).catch(function(aError) {
     ok(false, "Error occurred when getting availability: " + aError);
     teardown();
   });
 }
 
+function testUnsupportedDeviceAvailability() {
+  return Promise.race([
+    new Promise(function(aResolve, aReject) {
+      let request = new PresentationRequest("https://test.com");
+      request.getAvailability().then(function(aAvailability) {
+        availability = aAvailability;
+        aAvailability.onchange = function() {
+          availability.onchange = null;
+          ok(false, "Should not get onchange event.");
+          teardown();
+        }
+      });
+      gScript.sendAsyncMessage('trigger-add-unsupport-url-device');
+    }),
+    new Promise(function(aResolve, aReject) {
+      setTimeout(function() {
+        ok(true, "Should not get onchange event.");
+        availability.onchange = null;
+        gScript.sendAsyncMessage('trigger-remove-unsupported-device');
+        aResolve();
+      }, 3000);
+    }),
+  ]);
+}
+
+function testMultipleAvailabilityURLs() {
+  let request1 = new PresentationRequest(["https://example.com",
+                                         "https://example1.com"]);
+  let request2 = new PresentationRequest(["https://example1.com",
+                                         "https://example2.com"]);
+  return Promise.all([
+    request1.getAvailability().then(function(aAvailability) {
+      return new Promise(function(aResolve) {
+        aAvailability.onchange = function() {
+          aAvailability.onchange = null;
+          ok(true, "Should get onchange event.");
+          aResolve();
+        };
+      });
+    }),
+    request2.getAvailability().then(function(aAvailability) {
+      return new Promise(function(aResolve) {
+        aAvailability.onchange = function() {
+          aAvailability.onchange = null;
+          ok(true, "Should get onchange event.");
+          aResolve();
+        };
+      });
+    }),
+    new Promise(function(aResolve) {
+      gScript.sendAsyncMessage('trigger-add-multiple-devices');
+      aResolve();
+    }),
+  ]).then(new Promise(function(aResolve) {
+    gScript.sendAsyncMessage('trigger-remove-multiple-devices');
+    aResolve();
+  }));
+}
+
+function testPartialSupportedDeviceAvailability() {
+  let request1 = new PresentationRequest(["https://supportedUrl.com"]);
+  let request2 = new PresentationRequest(["http://notSupportedUrl.com"]);
+
+  return Promise.all([
+    request1.getAvailability().then(function(aAvailability) {
+      return new Promise(function(aResolve) {
+        aAvailability.onchange = function() {
+          aAvailability.onchange = null;
+          ok(true, "Should get onchange event.");
+          aResolve();
+        };
+      });
+    }),
+    Promise.race([
+      request2.getAvailability().then(function(aAvailability) {
+        return new Promise(function(aResolve) {
+          aAvailability.onchange = function() {
+            aAvailability.onchange = null;
+            ok(false, "Should get onchange event.");
+            aResolve();
+          };
+        });
+      }),
+      new Promise(function(aResolve) {
+        setTimeout(function() {
+          ok(true, "Should not get onchange event.");
+          availability.onchange = null;
+          aResolve();
+        }, 3000);
+      }),
+    ]),
+    new Promise(function(aResolve) {
+      gScript.sendAsyncMessage('trigger-add-https-devices');
+      aResolve();
+    }),
+  ]).then(new Promise(function(aResolve) {
+    gScript.sendAsyncMessage('trigger-remove-https-devices');
+    aResolve();
+  }));
+}
+
 function teardown() {
   request = null;
   availability = null;
   gScript.sendAsyncMessage('teardown');
   gScript.destroy();
   SimpleTest.finish();
 }
 
 function runTests() {
   ok(navigator.presentation, "navigator.presentation should be available.");
   testSetup().then(testInitialUnavailable)
              .then(testInitialAvailable)
              .then(testSameObject)
              .then(testOnChangeEvent)
              .then(testConsecutiveGetAvailability)
+             .then(testMultipleAvailabilityURLs)
+             .then(testUnsupportedDeviceAvailability)
+             .then(testPartialSupportedDeviceAvailability)
              .then(teardown);
 }
 
 SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
 SpecialPowers.pushPermissions([
   {type: "presentation-device-manage", allow: false, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
                                       ["dom.presentation.controller.enabled", true],
                                       ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });