Bug 1209602 - XUL: Implement disabling notifications for a site. r=MattN
--- a/browser/base/content/test/alerts/browser.ini
+++ b/browser/base/content/test/alerts/browser.ini
@@ -1,7 +1,9 @@
[DEFAULT]
support-files =
file_dom_notifications.html
[browser_notification_open_settings.js]
+[browser_notification_remove_permission.js]
+skip-if = e10s
[browser_notification_tab_switching.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/alerts/browser_notification_remove_permission.js
@@ -0,0 +1,79 @@
+"use strict";
+
+var tab;
+var notification;
+var notificationURL = "http://example.org/browser/browser/base/content/test/alerts/file_dom_notifications.html";
+var alertWindowClosed = false;
+var permRemoved = false;
+
+function test () {
+ waitForExplicitFinish();
+
+ let pm = Services.perms;
+ registerCleanupFunction(function() {
+ pm.remove(makeURI(notificationURL), "desktop-notification");
+ gBrowser.removeTab(tab);
+ window.restore();
+ });
+
+ pm.add(makeURI(notificationURL), "desktop-notification", pm.ALLOW_ACTION);
+
+ tab = gBrowser.addTab(notificationURL);
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", onLoad, true);
+}
+
+function onLoad() {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ let win = tab.linkedBrowser.contentWindow.wrappedJSObject;
+ notification = win.showNotification2();
+ notification.addEventListener("show", onAlertShowing);
+}
+
+function onAlertShowing() {
+ info("Notification alert showing");
+ notification.removeEventListener("show", onAlertShowing);
+
+ let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
+ if (!alertWindow) {
+ ok(true, "Notifications don't use XUL windows on all platforms.");
+ notification.close();
+ finish();
+ return;
+ }
+ ok(Services.perms.testExactPermission(makeURI(notificationURL), "desktop-notification"),
+ "Permission should exist prior to removal");
+ let disableForOriginMenuItem = alertWindow.document.getElementById("disableForOriginMenuItem");
+ is(disableForOriginMenuItem.localName, "menuitem", "menuitem found");
+ Services.obs.addObserver(permObserver, "perm-changed", false);
+ alertWindow.addEventListener("beforeunload", onAlertClosing);
+ disableForOriginMenuItem.click();
+ info("Clicked on disable-for-origin menuitem")
+}
+
+function permObserver(subject, topic, data) {
+ if (topic != "perm-changed") {
+ return;
+ }
+
+ let permission = subject.QueryInterface(Ci.nsIPermission);
+ is(permission.type, "desktop-notification", "desktop-notification permission changed");
+ is(data, "deleted", "desktop-notification permission deleted");
+
+ Services.obs.removeObserver(permObserver, "perm-changed");
+ permRemoved = true;
+ if (alertWindowClosed) {
+ finish();
+ }
+}
+
+function onAlertClosing(event) {
+ event.target.removeEventListener("beforeunload", onAlertClosing);
+
+ alertWindowClosed = true;
+ if (permRemoved) {
+ finish();
+ }
+}
+
+
--- a/browser/base/content/test/alerts/browser_notification_tab_switching.js
+++ b/browser/base/content/test/alerts/browser_notification_tab_switching.js
@@ -55,17 +55,17 @@ function onLoad() {
}
function onAlertShowing() {
info("Notification alert showing");
notification.removeEventListener("show", onAlertShowing);
let alertWindow = Services.wm.getMostRecentWindow("alert:alert");
if (!alertWindow) {
- todo(false, "Notifications don't use XUL windows on all platforms.");
+ ok(true, "Notifications don't use XUL windows on all platforms.");
notification.close();
newWindowOpenedFromTab.close();
finish();
return;
}
gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect);
EventUtils.synthesizeMouseAtCenter(alertWindow.document.getElementById("alertTitleLabel"), {}, alertWindow);
info("Clicked on notification");
--- a/toolkit/components/alerts/nsAlertsUtils.cpp
+++ b/toolkit/components/alerts/nsAlertsUtils.cpp
@@ -5,58 +5,27 @@
#include "nsAlertsUtils.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIStringBundle.h"
#include "nsIURI.h"
#include "nsXPIDLString.h"
-#define ALERTS_BUNDLE "chrome://alerts/locale/alert.properties"
-
/* static */
bool
nsAlertsUtils::IsActionablePrincipal(nsIPrincipal* aPrincipal)
{
return aPrincipal &&
!nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal) &&
!aPrincipal->GetIsNullPrincipal();
}
/* static */
void
-nsAlertsUtils::GetSource(nsIPrincipal* aPrincipal, nsAString& aSource)
-{
- nsAutoString hostPort;
- GetSourceHostPort(aPrincipal, hostPort);
- if (hostPort.IsEmpty()) {
- return;
- }
- nsCOMPtr<nsIStringBundleService> stringService(
- mozilla::services::GetStringBundleService());
- if (!stringService) {
- return;
- }
- nsCOMPtr<nsIStringBundle> alertsBundle;
- if (NS_WARN_IF(NS_FAILED(stringService->CreateBundle(ALERTS_BUNDLE,
- getter_AddRefs(alertsBundle))))) {
- return;
- }
- const char16_t* params[1] = { hostPort.get() };
- nsXPIDLString result;
- if (NS_WARN_IF(NS_FAILED(
- alertsBundle->FormatStringFromName(MOZ_UTF16("source.label"), params, 1,
- getter_Copies(result))))) {
- return;
- }
- aSource = result;
-}
-
-/* static */
-void
nsAlertsUtils::GetSourceHostPort(nsIPrincipal* aPrincipal,
nsAString& aHostPort)
{
if (!IsActionablePrincipal(aPrincipal)) {
return;
}
nsCOMPtr<nsIURI> principalURI;
if (NS_WARN_IF(NS_FAILED(
--- a/toolkit/components/alerts/nsAlertsUtils.h
+++ b/toolkit/components/alerts/nsAlertsUtils.h
@@ -18,22 +18,15 @@ public:
* Indicates whether an alert from |aPrincipal| should include the source
* string and action buttons. Returns false if |aPrincipal| is |nullptr|, or
* a system, expanded, or null principal.
*/
static bool
IsActionablePrincipal(nsIPrincipal* aPrincipal);
/**
- * Sets |aSource| to the localized notification source string, or an empty
- * string if |aPrincipal| is not actionable.
- */
- static void
- GetSource(nsIPrincipal* aPrincipal, nsAString& aSource);
-
- /**
* Sets |aHostPort| to the host and port from |aPrincipal|'s URI, or an
* empty string if |aPrincipal| is not actionable.
*/
static void
GetSourceHostPort(nsIPrincipal* aPrincipal, nsAString& aHostPort);
};
#endif /* nsAlertsUtils_h */
--- a/toolkit/components/alerts/nsXULAlerts.cpp
+++ b/toolkit/components/alerts/nsXULAlerts.cpp
@@ -140,17 +140,17 @@ nsXULAlerts::ShowAlertNotification(const
rv = argsArray->AppendElement(ifptr);
NS_ENSURE_SUCCESS(rv, rv);
// The source contains the host and port of the site that sent the
// notification. It is empty for system alerts.
nsCOMPtr<nsISupportsString> scriptableAlertSource (do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
NS_ENSURE_TRUE(scriptableAlertSource, NS_ERROR_FAILURE);
nsAutoString source;
- nsAlertsUtils::GetSource(aPrincipal, source);
+ nsAlertsUtils::GetSourceHostPort(aPrincipal, source);
scriptableAlertSource->SetData(source);
rv = argsArray->AppendElement(scriptableAlertSource);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMWindow> newWindow;
nsAutoCString features("chrome,dialog=yes,titlebar=no,popup=yes");
if (aInPrivateBrowsing) {
features.AppendLiteral(",private");
--- a/toolkit/components/alerts/resources/content/alert.js
+++ b/toolkit/components/alerts/resources/content/alert.js
@@ -27,24 +27,38 @@ function prefillAlertInfo() {
// arguments[2] --> the alert text
// arguments[3] --> is the text clickable?
// arguments[4] --> the alert cookie to be passed back to the listener
// arguments[5] --> the alert origin reported by the look and feel
// arguments[6] --> bidi
// arguments[7] --> lang
// arguments[8] --> replaced alert window (nsIDOMWindow)
// arguments[9] --> an optional callback listener (nsIObserver)
- // arguments[10] -> the localized alert source string
+ // arguments[10] -> the nsIURI.hostPort of the origin, optional
switch (window.arguments.length) {
default:
case 11: {
if (window.arguments[10]) {
- document.getElementById('alertBox').setAttribute('hasOrigin', true);
- document.getElementById('alertSourceLabel').setAttribute('value', window.arguments[10]);
+ let alertBox = document.getElementById("alertBox");
+ alertBox.setAttribute("hasOrigin", true);
+
+ let hostPort = window.arguments[10];
+ const ALERT_BUNDLE = Services.strings.createBundle(
+ "chrome://alerts/locale/alert.properties");
+ let label = document.getElementById("alertSourceLabel");
+ label.setAttribute("value",
+ ALERT_BUNDLE.formatStringFromName("source.label",
+ [hostPort],
+ 1));
+ let disableForOrigin = document.getElementById("disableForOriginMenuItem");
+ disableForOrigin.setAttribute("label",
+ ALERT_BUNDLE.formatStringFromName("webActions.disableForOrigin.label",
+ [hostPort],
+ 1));
}
}
case 10:
gAlertListener = window.arguments[9];
case 9:
gReplacedWindow = window.arguments[8];
case 8:
if (window.arguments[7]) {
@@ -212,16 +226,21 @@ function onAlertClick() {
if (alertBox.getAttribute("animate") == "true") {
// Closed when the animation ends.
alertBox.setAttribute("clicked", "true");
} else {
window.close();
}
}
+function disableForOrigin() {
+ gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
+ onAlertClose();
+}
+
function onAlertClose() {
let alertBox = document.getElementById("alertBox");
if (alertBox.getAttribute("animate") == "true") {
// Closed when the animation ends.
alertBox.setAttribute("closing", "true");
} else {
window.close();
}
--- a/toolkit/components/alerts/resources/content/alert.xul
+++ b/toolkit/components/alerts/resources/content/alert.xul
@@ -38,17 +38,23 @@
<image id="alertImage"/>
</hbox>
<vbox id="alertTextBox" class="alertTextBox">
<label id="alertTextLabel" class="alertText plain"/>
<spacer flex="1"/>
<box id="alertFooter">
<label id="alertSourceLabel" class="alertSource plain"/>
- <button type="menu" id="alertSettings" tooltiptext="&settings.label;"/>
+ <button type="menu" id="alertSettings" tooltiptext="&settings.label;"
+ onclick="event.stopPropagation();">
+ <menupopup position="after_end">
+ <menuitem id="disableForOriginMenuItem"
+ oncommand="disableForOrigin();"/>
+ </menupopup>
+ </button>
</box>
</vbox>
</box>
</vbox>
<!-- This method is called inline because we want to make sure we establish the width
and height of the alert before we fire the onload handler. -->
<script type="application/javascript">prefillAlertInfo();</script>
--- a/toolkit/locales/en-US/chrome/alerts/alert.properties
+++ b/toolkit/locales/en-US/chrome/alerts/alert.properties
@@ -3,15 +3,17 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# LOCALIZATION NOTE(closeButton.title): Used as the close button text for web notifications on OS X.
# This should ideally match the string that OS X uses for the close button on alert-type
# notifications. OS X will truncate the value if it's too long.
closeButton.title = Close
# LOCALIZATION NOTE(actionButton.label): Used as the button label to provide more actions on OS X notifications. OS X will truncate this if it's too long.
actionButton.label = …
-webActions.disable.label = Disable notifications from this site
+# LOCALIZATION NOTE(webActions.disableForOrigin.label): %S is replaced
+# with the hostname origin of the notification.
+webActions.disableForOrigin.label = Disable notifications from %S
# LOCALIZATION NOTE(source.label): Used to show the URL of the site that
# sent the notification (e.g., "via mozilla.org"). "%1$S" is the source host
# and port.
source.label=via %1$S
webActions.settings.label = Notification settings
--- a/toolkit/themes/shared/alert-common.css
+++ b/toolkit/themes/shared/alert-common.css
@@ -60,25 +60,24 @@ label {
}
#alertSettings {
-moz-appearance: none;
background-color: transparent;
border-width: 0;
min-width: 0;
list-style-image: url("chrome://mozapps/skin/extensions/utilities.svg#utilities");
- visibility: hidden; /* Temporary until bug 1209602 or bug 1205172 is fixed. */
}
#alertSettings:hover {
background-color: rgba(107,107,107,.2);
border-radius: 20px;
}
-#alertSettings["open"],
+#alertSettings[open],
#alertSettings:hover:active {
background-color: rgba(107,107,107,.4);
}
#alertSettings > .button-box > .button-menu-dropmarker,
#alertSettings > .button-box > .box-inherit > .button-text {
display: none;
}
--- a/widget/cocoa/OSXNotificationCenter.mm
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -240,37 +240,49 @@ OSXNotificationCenter::ShowAlertNotifica
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
Class unClass = NSClassFromString(@"NSUserNotification");
id<FakeNSUserNotification> notification = [[unClass alloc] init];
notification.title = nsCocoaUtils::ToNSString(aAlertTitle);
nsAutoString hostPort;
nsAlertsUtils::GetSourceHostPort(aPrincipal, hostPort);
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsresult rv = sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle));
+
if (!hostPort.IsEmpty()) {
- notification.subtitle = nsCocoaUtils::ToNSString(hostPort);
+ const char16_t* formatStrings[] = { hostPort.get() };
+ nsXPIDLString notificationSource;
+ bundle->FormatStringFromName(NS_LITERAL_STRING("source.label").get(),
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(notificationSource));
+ notification.subtitle = nsCocoaUtils::ToNSString(notificationSource);
}
notification.informativeText = nsCocoaUtils::ToNSString(aAlertText);
notification.soundName = NSUserNotificationDefaultSoundName;
notification.hasActionButton = NO;
// If this is not an application/extension alert, show additional actions dealing with permissions.
if (nsAlertsUtils::IsActionablePrincipal(aPrincipal)) {
- nsCOMPtr<nsIStringBundleService> sbs = do_GetService(NS_STRINGBUNDLE_CONTRACTID);
- nsCOMPtr<nsIStringBundle> bundle;
- nsresult rv = sbs->CreateBundle("chrome://alerts/locale/alert.properties", getter_AddRefs(bundle));
if (NS_SUCCEEDED(rv)) {
nsXPIDLString closeButtonTitle, actionButtonTitle, disableButtonTitle, settingsButtonTitle;
bundle->GetStringFromName(NS_LITERAL_STRING("closeButton.title").get(),
getter_Copies(closeButtonTitle));
bundle->GetStringFromName(NS_LITERAL_STRING("actionButton.label").get(),
getter_Copies(actionButtonTitle));
- bundle->GetStringFromName(NS_LITERAL_STRING("webActions.disable.label").get(),
- getter_Copies(disableButtonTitle));
+ if (!hostPort.IsEmpty()) {
+ const char16_t* formatStrings[] = { hostPort.get() };
+ bundle->FormatStringFromName(NS_LITERAL_STRING("webActions.disableForOrigin.label").get(),
+ formatStrings,
+ ArrayLength(formatStrings),
+ getter_Copies(disableButtonTitle));
+ }
bundle->GetStringFromName(NS_LITERAL_STRING("webActions.settings.label").get(),
getter_Copies(settingsButtonTitle));
notification.hasActionButton = YES;
notification.otherButtonTitle = nsCocoaUtils::ToNSString(closeButtonTitle);
notification.actionButtonTitle = nsCocoaUtils::ToNSString(actionButtonTitle);
[(NSObject*)notification setValue:@(YES) forKey:@"_showsButtons"];
[(NSObject*)notification setValue:@(YES) forKey:@"_alwaysShowAlternateActionMenu"];