Bug 1227730 - Support closing libnotify alerts. r?MattN r=karlt draft
authorKit Cambridge <kcambridge@mozilla.com>
Tue, 16 Feb 2016 13:27:27 -0800
changeset 333458 02f7a3d316fd084e71a918caf50d44a7123fc521
parent 332881 789a12291942763bc1e3a89f97e0b82dc1c9d00b
child 514762 1f83aaa16b7212d60697bfda4cc5dc3527e88400
push id11381
push userkcambridge@mozilla.com
push dateTue, 23 Feb 2016 20:31:53 +0000
reviewersMattN, karlt
bugs1227730
milestone47.0a1
Bug 1227730 - Support closing libnotify alerts. r?MattN r=karlt MozReview-Commit-ID: LR3OiP0miEm
browser/base/content/test/alerts/browser.ini
browser/base/content/test/alerts/browser_notification_close.js
browser/base/content/test/alerts/browser_notification_replace.js
browser/base/content/test/alerts/file_dom_notifications.html
toolkit/components/alerts/nsIAlertsService.idl
toolkit/system/gnome/nsAlertsIconListener.cpp
toolkit/system/gnome/nsAlertsIconListener.h
toolkit/system/gnome/nsSystemAlertsService.cpp
toolkit/system/gnome/nsSystemAlertsService.h
--- a/browser/base/content/test/alerts/browser.ini
+++ b/browser/base/content/test/alerts/browser.ini
@@ -3,10 +3,11 @@ support-files =
   head.js
   file_dom_notifications.html
 
 [browser_notification_close.js]
 [browser_notification_do_not_disturb.js]
 [browser_notification_open_settings.js]
 [browser_notification_remove_permission.js]
 [browser_notification_permission_migration.js]
+[browser_notification_replace.js]
 [browser_notification_tab_switching.js]
 skip-if = buildapp == 'mulet'
--- a/browser/base/content/test/alerts/browser_notification_close.js
+++ b/browser/base/content/test/alerts/browser_notification_close.js
@@ -23,17 +23,17 @@ add_task(function* test_notificationClos
       ok(true, "Notifications don't use XUL windows on all platforms.");
       notification.close();
       return;
     }
 
     let alertTitleLabel = alertWindow.document.getElementById("alertTitleLabel");
     is(alertTitleLabel.value, "Test title", "Title text of notification should be present");
     let alertTextLabel = alertWindow.document.getElementById("alertTextLabel");
-    is(alertTextLabel.textContent, "Test body", "Body text of notification should be present");
+    is(alertTextLabel.textContent, "Test body 2", "Body text of notification should be present");
 
     let alertCloseButton = alertWindow.document.querySelector(".alertCloseButton");
     is(alertCloseButton.localName, "toolbarbutton", "close button found");
     let promiseBeforeUnloadEvent =
       BrowserTestUtils.waitForEvent(alertWindow, "beforeunload");
     let closedTime = alertWindow.Date.now();
     alertCloseButton.click();
     info("Clicked on close button");
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_replace.js
@@ -0,0 +1,38 @@
+"use strict";
+
+let notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+
+add_task(function* test_notificationReplace() {
+  let pm = Services.perms;
+  pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: notificationURL
+  }, function* dummyTabTask(aBrowser) {
+    yield ContentTask.spawn(aBrowser, {}, function* () {
+      let win = content.window.wrappedJSObject;
+      let notification = win.showNotification1();
+      let promiseCloseEvent = ContentTaskUtils.waitForEvent(notification, "close");
+
+      let showEvent = yield ContentTaskUtils.waitForEvent(notification, "show");
+      is(showEvent.target.body, "Test body 1", "Showed tagged notification");
+
+      let newNotification = win.showNotification2();
+      let newShowEvent = yield ContentTaskUtils.waitForEvent(newNotification, "show");
+      is(newShowEvent.target.body, "Test body 2", "Showed new notification with same tag");
+
+      let closeEvent = yield promiseCloseEvent;
+      is(closeEvent.target.body, "Test body 1", "Closed previous tagged notification");
+
+      let promiseNewCloseEvent = ContentTaskUtils.waitForEvent(newNotification, "close");
+      newNotification.close();
+      let newCloseEvent = yield promiseNewCloseEvent;
+      is(newCloseEvent.target.body, "Test body 2", "Closed new notification");
+    });
+  });
+});
+
+add_task(function* cleanup() {
+  Services.perms.remove(makeURI(notificationURL), "desktop-notification");
+});
--- a/browser/base/content/test/alerts/file_dom_notifications.html
+++ b/browser/base/content/test/alerts/file_dom_notifications.html
@@ -3,32 +3,32 @@
 <meta charset="utf-8">
 <script>
 "use strict";
 
 function showNotification1() {
   var options = {
       dir: undefined,
       lang: undefined,
-      body: "Test body",
+      body: "Test body 1",
       tag: "Test tag",
       icon: undefined,
   };
   var n = new Notification("Test title", options);
   n.addEventListener("click", function(event) {
     event.preventDefault();
   });
   return n;
 }
 
 function showNotification2() {
   var options = {
       dir: undefined,
       lang: undefined,
-      body: "Test body",
+      body: "Test body 2",
       tag: "Test tag",
       icon: undefined,
   };
   return new Notification("Test title", options);
 }
 </script>
 </head>
 <body>
--- a/toolkit/components/alerts/nsIAlertsService.idl
+++ b/toolkit/components/alerts/nsIAlertsService.idl
@@ -25,19 +25,19 @@ interface nsIAlertNotification : nsISupp
             [optional] in AString cookie,
             [optional] in AString dir,
             [optional] in AString lang,
             [optional] in AString data,
             [optional] in nsIPrincipal principal,
             [optional] in boolean inPrivateBrowsing);
 
   /**
-   * The name of the notification. This is currently only used on Android and
-   * OS X. On Android, the name is hashed and used as a notification ID.
-   * Notifications will replace previous notifications with the same name.
+   * The name of the notification. On Android, the name is hashed and used as
+   * a notification ID. Notifications will replace previous notifications with
+   * the same name.
    */
   readonly attribute AString name;
 
   /**
    * A URL identifying the image to put in the alert. The OS X backend limits
    * the amount of time it will wait for the image to load to six seconds. After
    * that time, the alert will show without an image.
    */
@@ -113,42 +113,21 @@ interface nsIAlertNotification : nsISupp
 };
 
 [scriptable, uuid(f7a36392-d98b-4141-a7d7-4e46642684e3)]
 interface nsIAlertsService : nsISupports
 {
   void showAlert(in nsIAlertNotification alert,
                  [optional] in nsIObserver alertListener);
   /**
-   * Displays a sliding notification window.
+   * Initializes and shows an |nsIAlertNotification| with the given parameters.
    *
-   * @param imageUrl       A URL identifying the image to put in the alert.
-   *                       The OS X implemenation limits the amount of time it
-   *                       will wait for an icon to load to six seconds. After
-   *                       that time the alert will show with no icon.
-   * @param title          The title for the alert.
-   * @param text           The contents of the alert.
-   * @param textClickable  If true, causes the alert text to look like a link
-   *                       and notifies the listener when user attempts to
-   *                       click the alert text.
-   * @param cookie         A blind cookie the alert will pass back to the
-   *                       consumer during the alert listener callbacks.
    * @param alertListener  Used for callbacks. May be null if the caller
    *                       doesn't care about callbacks.
-   * @param name           The name of the notification. This is currently only
-   *                       used on Android and OS X. On Android the name is
-   *                       hashed and used as a notification ID. Notifications
-   *                       will replace previous notifications with the same name.
-   * @param dir            Bidi override for the title. Valid values are
-   *                       "auto", "ltr" or "rtl". Only available on supported
-   *                       platforms.
-   * @param lang           Language of title and text of the alert. Only available
-   *                       on supported platforms.
-   * @param inPrivateBrowsing If set to true, imageUrl will be loaded in private
-   *                          browsing mode.
+   * @see nsIAlertNotification for descriptions of all other parameters.
    * @throws NS_ERROR_NOT_AVAILABLE If the notification cannot be displayed.
    *
    * The following arguments will be passed to the alertListener's observe()
    * method:
    *   subject - null
    *   topic   - "alertfinished" when the alert goes away
    *             "alertdisablecallback" when alerts should be disabled for the principal
    *             "alertsettingscallback" when alert settings should be opened
--- a/toolkit/system/gnome/nsAlertsIconListener.cpp
+++ b/toolkit/system/gnome/nsAlertsIconListener.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsAlertsIconListener.h"
 #include "imgIContainer.h"
 #include "imgILoader.h"
 #include "imgIRequest.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
+#include "nsSystemAlertsService.h"
 #include "nsIAlertsService.h"
 #include "nsIImageToPixbuf.h"
 #include "nsIStringBundle.h"
 #include "nsIObserverService.h"
 #include "nsIURI.h"
 #include "nsCRT.h"
 
 #include <dlfcn.h>
@@ -26,16 +27,17 @@ void* nsAlertsIconListener::libNotifyHan
 bool nsAlertsIconListener::libNotifyNotAvail = false;
 nsAlertsIconListener::notify_is_initted_t nsAlertsIconListener::notify_is_initted = nullptr;
 nsAlertsIconListener::notify_init_t nsAlertsIconListener::notify_init = nullptr;
 nsAlertsIconListener::notify_get_server_caps_t nsAlertsIconListener::notify_get_server_caps = nullptr;
 nsAlertsIconListener::notify_notification_new_t nsAlertsIconListener::notify_notification_new = nullptr;
 nsAlertsIconListener::notify_notification_show_t nsAlertsIconListener::notify_notification_show = nullptr;
 nsAlertsIconListener::notify_notification_set_icon_from_pixbuf_t nsAlertsIconListener::notify_notification_set_icon_from_pixbuf = nullptr;
 nsAlertsIconListener::notify_notification_add_action_t nsAlertsIconListener::notify_notification_add_action = nullptr;
+nsAlertsIconListener::notify_notification_close_t nsAlertsIconListener::notify_notification_close = nullptr;
 
 static void notify_action_cb(NotifyNotification *notification,
                              gchar *action, gpointer user_data)
 {
   nsAlertsIconListener* alert = static_cast<nsAlertsIconListener*> (user_data);
   alert->SendCallback();
 }
 
@@ -67,18 +69,21 @@ GetPixbufFromImgRequest(imgIRequest* aRe
     do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
 
   return imgToPixbuf->ConvertImageToPixbuf(image);
 }
 
 NS_IMPL_ISUPPORTS(nsAlertsIconListener, imgINotificationObserver,
                   nsIObserver, nsISupportsWeakReference)
 
-nsAlertsIconListener::nsAlertsIconListener()
-: mLoadedFrame(false),
+nsAlertsIconListener::nsAlertsIconListener(nsSystemAlertsService* aBackend,
+                                           const nsAString& aAlertName)
+: mAlertName(aAlertName),
+  mBackend(aBackend),
+  mLoadedFrame(false),
   mNotification(nullptr)
 {
   if (!libNotifyHandle && !libNotifyNotAvail) {
     libNotifyHandle = dlopen("libnotify.so.4", RTLD_LAZY);
     if (!libNotifyHandle) {
       libNotifyHandle = dlopen("libnotify.so.1", RTLD_LAZY);
       if (!libNotifyHandle) {
         libNotifyNotAvail = true;
@@ -88,25 +93,27 @@ nsAlertsIconListener::nsAlertsIconListen
 
     notify_is_initted = (notify_is_initted_t)dlsym(libNotifyHandle, "notify_is_initted");
     notify_init = (notify_init_t)dlsym(libNotifyHandle, "notify_init");
     notify_get_server_caps = (notify_get_server_caps_t)dlsym(libNotifyHandle, "notify_get_server_caps");
     notify_notification_new = (notify_notification_new_t)dlsym(libNotifyHandle, "notify_notification_new");
     notify_notification_show = (notify_notification_show_t)dlsym(libNotifyHandle, "notify_notification_show");
     notify_notification_set_icon_from_pixbuf = (notify_notification_set_icon_from_pixbuf_t)dlsym(libNotifyHandle, "notify_notification_set_icon_from_pixbuf");
     notify_notification_add_action = (notify_notification_add_action_t)dlsym(libNotifyHandle, "notify_notification_add_action");
-    if (!notify_is_initted || !notify_init || !notify_get_server_caps || !notify_notification_new || !notify_notification_show || !notify_notification_set_icon_from_pixbuf || !notify_notification_add_action) {
+    notify_notification_close = (notify_notification_close_t)dlsym(libNotifyHandle, "notify_notification_close");
+    if (!notify_is_initted || !notify_init || !notify_get_server_caps || !notify_notification_new || !notify_notification_show || !notify_notification_set_icon_from_pixbuf || !notify_notification_add_action || !notify_notification_close) {
       dlclose(libNotifyHandle);
       libNotifyHandle = nullptr;
     }
   }
 }
 
 nsAlertsIconListener::~nsAlertsIconListener()
 {
+  mBackend->RemoveListener(mAlertName, this);
   if (mIconRequest)
     mIconRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
   // Don't dlclose libnotify as it uses atexit().
 }
 
 NS_IMETHODIMP
 nsAlertsIconListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
 {
@@ -176,16 +183,19 @@ nsAlertsIconListener::OnFrameComplete(im
   mIconRequest = nullptr;
 
   return NS_OK;
 }
 
 nsresult
 nsAlertsIconListener::ShowAlert(GdkPixbuf* aPixbuf)
 {
+  if (!mBackend->IsActiveListener(mAlertName, this))
+    return NS_OK;
+
   mNotification = notify_notification_new(mAlertTitle.get(), mAlertText.get(),
                                           nullptr, nullptr);
 
   if (!mNotification)
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsCOMPtr<nsIObserverService> obsServ =
       do_GetService("@mozilla.org/observer-service;1");
@@ -206,22 +216,27 @@ nsAlertsIconListener::ShowAlert(GdkPixbu
 
   // Fedora 10 calls NotifyNotification "closed" signal handlers with a
   // different signature, so a marshaller is used instead of a C callback to
   // get the user_data (this) in a parseable format.  |closure| is created
   // with a floating reference, which gets sunk by g_signal_connect_closure().
   GClosure* closure = g_closure_new_simple(sizeof(GClosure), this);
   g_closure_set_marshal(closure, notify_closed_marshal);
   mClosureHandler = g_signal_connect_closure(mNotification, "closed", closure, FALSE);
-  gboolean result = notify_notification_show(mNotification, nullptr);
+  GError* error = nullptr;
+  if (!notify_notification_show(mNotification, &error)) {
+    NS_WARNING(error->message);
+    g_error_free(error);
+    return NS_ERROR_FAILURE;
+  }
 
-  if (result && mAlertListener)
+  if (mAlertListener)
     mAlertListener->Observe(nullptr, "alertshow", mAlertCookie.get());
 
-  return result ? NS_OK : NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 nsresult
 nsAlertsIconListener::StartRequest(const nsAString & aImageUrl, bool aInPrivateBrowsing)
 {
   if (mIconRequest) {
     // Another icon request is already in flight.  Kill it.
     mIconRequest->Cancel(NS_BINDING_ABORTED);
@@ -259,18 +274,17 @@ nsAlertsIconListener::SendCallback()
 
 void
 nsAlertsIconListener::SendClosed()
 {
   if (mNotification) {
     g_object_unref(mNotification);
     mNotification = nullptr;
   }
-  if (mAlertListener)
-    mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
+  NotifyFinished();
 }
 
 NS_IMETHODIMP
 nsAlertsIconListener::Observe(nsISupports *aSubject, const char *aTopic,
                               const char16_t *aData) {
   // We need to close any open notifications upon application exit, otherwise
   // we will leak since libnotify holds a ref for us.
   if (!nsCRT::strcmp(aTopic, "quit-application") && mNotification) {
@@ -278,16 +292,39 @@ nsAlertsIconListener::Observe(nsISupport
     g_object_unref(mNotification);
     mNotification = nullptr;
     Release(); // equivalent to NS_RELEASE(this)
   }
   return NS_OK;
 }
 
 nsresult
+nsAlertsIconListener::Close()
+{
+  if (mIconRequest) {
+    mIconRequest->Cancel(NS_BINDING_ABORTED);
+    mIconRequest = nullptr;
+  }
+
+  if (!mNotification) {
+    NotifyFinished();
+    return NS_OK;
+  }
+
+  GError* error = nullptr;
+  if (!notify_notification_close(mNotification, &error)) {
+    NS_WARNING(error->message);
+    g_error_free(error);
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 nsAlertsIconListener::InitAlertAsync(nsIAlertNotification* aAlert,
                                      nsIObserver* aAlertListener)
 {
   if (!libNotifyHandle)
     return NS_ERROR_FAILURE;
 
   if (!notify_is_initted()) {
     // Give the name of this application to libnotify
@@ -366,8 +403,14 @@ nsAlertsIconListener::InitAlertAsync(nsI
   rv = aAlert->GetImageURL(imageUrl);
   NS_ENSURE_SUCCESS(rv, rv);
   bool inPrivateBrowsing;
   rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return StartRequest(imageUrl, inPrivateBrowsing);
 }
+
+void nsAlertsIconListener::NotifyFinished()
+{
+  if (mAlertListener)
+    mAlertListener->Observe(nullptr, "alertfinished", mAlertCookie.get());
+}
--- a/toolkit/system/gnome/nsAlertsIconListener.h
+++ b/toolkit/system/gnome/nsAlertsIconListener.h
@@ -11,32 +11,35 @@
 #include "nsString.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
 class imgIRequest;
 class nsIAlertNotification;
+class nsSystemAlertsService;
 
 struct NotifyNotification;
 
 class nsAlertsIconListener : public imgINotificationObserver,
                              public nsIObserver,
                              public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_IMGINOTIFICATIONOBSERVER
   NS_DECL_NSIOBSERVER
 
-  nsAlertsIconListener();
+  nsAlertsIconListener(nsSystemAlertsService* aBackend,
+                       const nsAString& aAlertName);
 
   nsresult InitAlertAsync(nsIAlertNotification* aAlert,
                           nsIObserver* aAlertListener);
+  nsresult Close();
 
   void SendCallback();
   void SendClosed();
 
 protected:
   virtual ~nsAlertsIconListener();
 
   nsresult OnLoadComplete(imgIRequest* aRequest);
@@ -48,39 +51,46 @@ protected:
    * four in libnotify.so.1.
    * Passing the fourth argument as NULL is binary compatible.
    */
   typedef void (*NotifyActionCallback)(NotifyNotification*, char*, gpointer);
   typedef bool (*notify_is_initted_t)(void);
   typedef bool (*notify_init_t)(const char*);
   typedef GList* (*notify_get_server_caps_t)(void);
   typedef NotifyNotification* (*notify_notification_new_t)(const char*, const char*, const char*, const char*);
-  typedef bool (*notify_notification_show_t)(void*, char*);
+  typedef bool (*notify_notification_show_t)(void*, GError**);
   typedef void (*notify_notification_set_icon_from_pixbuf_t)(void*, GdkPixbuf*);
   typedef void (*notify_notification_add_action_t)(void*, const char*, const char*, NotifyActionCallback, gpointer, GFreeFunc);
+  typedef bool (*notify_notification_close_t)(void*, GError**);
 
   nsCOMPtr<imgIRequest> mIconRequest;
   nsCString mAlertTitle;
   nsCString mAlertText;
 
   nsCOMPtr<nsIObserver> mAlertListener;
   nsString mAlertCookie;
+  nsString mAlertName;
+
+  RefPtr<nsSystemAlertsService> mBackend;
 
   bool mLoadedFrame;
   bool mAlertHasAction;
 
   static void* libNotifyHandle;
   static bool libNotifyNotAvail;
   static notify_is_initted_t notify_is_initted;
   static notify_init_t notify_init;
   static notify_get_server_caps_t notify_get_server_caps;
   static notify_notification_new_t notify_notification_new;
   static notify_notification_show_t notify_notification_show;
   static notify_notification_set_icon_from_pixbuf_t notify_notification_set_icon_from_pixbuf;
   static notify_notification_add_action_t notify_notification_add_action;
+  static notify_notification_close_t notify_notification_close;
   NotifyNotification* mNotification;
   gulong mClosureHandler;
 
   nsresult StartRequest(const nsAString & aImageUrl, bool aInPrivateBrowsing);
   nsresult ShowAlert(GdkPixbuf* aPixbuf);
+
+  void NotifyFinished();
 };
 
 #endif
--- a/toolkit/system/gnome/nsSystemAlertsService.cpp
+++ b/toolkit/system/gnome/nsSystemAlertsService.cpp
@@ -52,20 +52,58 @@ NS_IMETHODIMP nsSystemAlertsService::Sho
   return ShowAlert(alert, aAlertListener);
 }
 
 NS_IMETHODIMP nsSystemAlertsService::ShowAlert(nsIAlertNotification* aAlert,
                                                nsIObserver* aAlertListener)
 {
   NS_ENSURE_ARG(aAlert);
 
-  RefPtr<nsAlertsIconListener> alertListener = new nsAlertsIconListener();
+  nsAutoString alertName;
+  nsresult rv = aAlert->GetName(alertName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  RefPtr<nsAlertsIconListener> alertListener =
+    new nsAlertsIconListener(this, alertName);
   if (!alertListener)
     return NS_ERROR_OUT_OF_MEMORY;
 
+  AddListener(alertName, alertListener);
   return alertListener->InitAlertAsync(aAlert, aAlertListener);
 }
 
 NS_IMETHODIMP nsSystemAlertsService::CloseAlert(const nsAString& aAlertName,
                                                 nsIPrincipal* aPrincipal)
 {
-  return NS_ERROR_NOT_IMPLEMENTED;
+  RefPtr<nsAlertsIconListener> listener = mActiveListeners.Get(aAlertName);
+  if (!listener) {
+    return NS_OK;
+  }
+  mActiveListeners.Remove(aAlertName);
+  return listener->Close();
+}
+
+bool nsSystemAlertsService::IsActiveListener(const nsAString& aAlertName,
+                                             nsAlertsIconListener* aListener)
+{
+  return mActiveListeners.Get(aAlertName) == aListener;
 }
+
+void nsSystemAlertsService::AddListener(const nsAString& aAlertName,
+                                        nsAlertsIconListener* aListener)
+{
+  RefPtr<nsAlertsIconListener> oldListener = mActiveListeners.Get(aAlertName);
+  mActiveListeners.Put(aAlertName, aListener);
+  if (oldListener) {
+    // If an alert with this name already exists, close it.
+    oldListener->Close();
+  }
+}
+
+void nsSystemAlertsService::RemoveListener(const nsAString& aAlertName,
+                                           nsAlertsIconListener* aListener)
+{
+  if (IsActiveListener(aAlertName, aListener)) {
+    // The alert may have been replaced; only remove it from the active
+    // listeners map if it's the same.
+    mActiveListeners.Remove(aAlertName);
+  }
+}
--- a/toolkit/system/gnome/nsSystemAlertsService.h
+++ b/toolkit/system/gnome/nsSystemAlertsService.h
@@ -2,26 +2,38 @@
 /* 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 nsSystemAlertsService_h__
 #define nsSystemAlertsService_h__
 
 #include "nsIAlertsService.h"
+#include "nsDataHashtable.h"
 #include "nsCOMPtr.h"
 
+class nsAlertsIconListener;
+
 class nsSystemAlertsService : public nsIAlertsService
 {
 public:
   NS_DECL_NSIALERTSSERVICE
   NS_DECL_ISUPPORTS
 
   nsSystemAlertsService();
 
   nsresult Init();
 
+  bool IsActiveListener(const nsAString& aAlertName,
+                        nsAlertsIconListener* aListener);
+  void RemoveListener(const nsAString& aAlertName,
+                      nsAlertsIconListener* aListener);
+
 protected:
   virtual ~nsSystemAlertsService();
 
+  void AddListener(const nsAString& aAlertName,
+                   nsAlertsIconListener* aListener);
+
+  nsDataHashtable<nsStringHashKey, nsAlertsIconListener*> mActiveListeners;
 };
 
 #endif /* nsSystemAlertsService_h__ */