--- 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__ */