Bug 1205399 - OS X: Implement disabling Notifications for the site from the native notifications. r=mstange
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Fri, 25 Sep 2015 13:18:29 -0700
changeset 264690 9898e4c1387675ceff6e5c514b2b37dc782ae94a
parent 264689 1c884fcfdf388ceb3bf965d0939e6304e4d0c2c9
child 264691 cc22ed2cafd3f850e00c3b16a45e6a24c1a190bc
push id65707
push usercbook@mozilla.com
push dateMon, 28 Sep 2015 12:18:34 +0000
treeherdermozilla-inbound@2c0e60a8f736 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1205399
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1205399 - OS X: Implement disabling Notifications for the site from the native notifications. r=mstange
toolkit/locales/en-US/chrome/alerts/alert.properties
toolkit/locales/jar.mn
widget/cocoa/OSXNotificationCenter.h
widget/cocoa/OSXNotificationCenter.mm
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/alerts/alert.properties
@@ -0,0 +1,11 @@
+# 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/.
+
+# 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
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -118,16 +118,17 @@
   locale/@AB_CD@/mozapps/update/updates.properties                (%chrome/mozapps/update/updates.properties)
   locale/@AB_CD@/mozapps/update/history.dtd                       (%chrome/mozapps/update/history.dtd)
   locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.dtd           (%chrome/mozapps/extensions/xpinstallConfirm.dtd)
   locale/@AB_CD@/mozapps/xpinstall/xpinstallConfirm.properties    (%chrome/mozapps/extensions/xpinstallConfirm.properties)
 % locale pluginproblem @AB_CD@ %locale/@AB_CD@/pluginproblem/
   locale/@AB_CD@/pluginproblem/pluginproblem.dtd                  (%chrome/pluginproblem/pluginproblem.dtd)
 % locale alerts @AB_CD@ %locale/@AB_CD@/alerts/
   locale/@AB_CD@/alerts/alert.dtd                                (%chrome/alerts/alert.dtd)
+  locale/@AB_CD@/alerts/alert.properties                         (%chrome/alerts/alert.properties)
 % locale cookie @AB_CD@ %locale/@AB_CD@/cookie/
   locale/@AB_CD@/cookie/cookieAcceptDialog.dtd           (%chrome/cookie/cookieAcceptDialog.dtd)
   locale/@AB_CD@/cookie/cookieAcceptDialog.properties    (%chrome/cookie/cookieAcceptDialog.properties)
 % locale formautofill @AB_CD@ %locale/@AB_CD@/formautofill/
   locale/@AB_CD@/formautofill/requestAutocomplete.dtd (%chrome/formautofill/requestAutocomplete.dtd)
 % locale passwordmgr @AB_CD@ %locale/@AB_CD@/passwordmgr/
   locale/@AB_CD@/passwordmgr/passwordmgr.properties (%chrome/passwordmgr/passwordmgr.properties)
   locale/@AB_CD@/passwordmgr/passwordManager.dtd    (%chrome/passwordmgr/passwordManager.dtd)
--- a/widget/cocoa/OSXNotificationCenter.h
+++ b/widget/cocoa/OSXNotificationCenter.h
@@ -1,25 +1,29 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 OSXNotificationCenter_h
 #define OSXNotificationCenter_h
 
 #import <Foundation/Foundation.h>
 #include "nsIAlertsService.h"
 #include "imgINotificationObserver.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
 #include "mozilla/RefPtr.h"
 
 @class mozNotificationCenterDelegate;
 
+#if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
+typedef NSInteger NSUserNotificationActivationType;
+#endif
+
 namespace mozilla {
 
 class OSXNotificationInfo;
 
 class OSXNotificationCenter : public nsIAlertsService,
                               public imgINotificationObserver,
                               public nsITimerCallback
 {
@@ -28,17 +32,18 @@ public:
   NS_DECL_NSIALERTSSERVICE
   NS_DECL_IMGINOTIFICATIONOBSERVER
   NS_DECL_NSITIMERCALLBACK
 
   OSXNotificationCenter();
 
   nsresult Init();
   void CloseAlertCocoaString(NSString *aAlertName);
-  void OnClick(NSString *aAlertName);
+  void OnActivate(NSString *aAlertName, NSUserNotificationActivationType aActivationType,
+                  unsigned long long aAdditionalActionIndex);
   void ShowPendingNotification(OSXNotificationInfo *osxni);
 
 protected:
   virtual ~OSXNotificationCenter();
 
 private:
   mozNotificationCenterDelegate *mDelegate;
   nsTArray<nsRefPtr<OSXNotificationInfo> > mActiveAlerts;
--- a/widget/cocoa/OSXNotificationCenter.mm
+++ b/widget/cocoa/OSXNotificationCenter.mm
@@ -1,39 +1,52 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "OSXNotificationCenter.h"
 #import <AppKit/AppKit.h>
 #include "imgIRequest.h"
 #include "imgIContainer.h"
+#include "nsIStringBundle.h"
 #include "nsNetUtil.h"
 #include "imgLoader.h"
 #import "nsCocoaUtils.h"
+#include "nsContentUtils.h"
 #include "nsObjCExceptions.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsIContentPolicy.h"
 #include "imgRequestProxy.h"
 
 using namespace mozilla;
 
 #if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
 @protocol NSUserNotificationCenterDelegate
 @end
 static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
 enum {
   NSUserNotificationActivationTypeNone = 0,
   NSUserNotificationActivationTypeContentsClicked = 1,
-  NSUserNotificationActivationTypeActionButtonClicked = 2
+  NSUserNotificationActivationTypeActionButtonClicked = 2,
 };
-typedef NSInteger NSUserNotificationActivationType;
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_9) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_9)
+enum {
+  NSUserNotificationActivationTypeReplied = 3,
+};
+#endif
+
+#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
+enum {
+  NSUserNotificationActivationTypeAdditionalActionClicked = 4
+};
 #endif
 
 @protocol FakeNSUserNotification <NSObject>
 @property (copy) NSString* title;
 @property (copy) NSString* subtitle;
 @property (copy) NSString* informativeText;
 @property (copy) NSString* actionButtonTitle;
 @property (copy) NSDictionary* userInfo;
@@ -85,17 +98,20 @@ typedef NSInteger NSUserNotificationActi
         didDeliverNotification:(id<FakeNSUserNotification>)notification
 {
 
 }
 
 - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
        didActivateNotification:(id<FakeNSUserNotification>)notification
 {
-  mOSXNC->OnClick([[notification userInfo] valueForKey:@"name"]);
+  NSNumber *alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"];
+  mOSXNC->OnActivate([[notification userInfo] valueForKey:@"name"],
+                     notification.activationType,
+                     [alternateActionIndex unsignedLongLongValue]);
 }
 
 - (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
      shouldPresentNotification:(id<FakeNSUserNotification>)notification
 {
   return YES;
 }
 
@@ -105,20 +121,32 @@ typedef NSInteger NSUserNotificationActi
   didRemoveDeliveredNotifications:(NSArray *)notifications
 {
   for (id<FakeNSUserNotification> notification in notifications) {
     NSString *name = [[notification userInfo] valueForKey:@"name"];
     mOSXNC->CloseAlertCocoaString(name);
   }
 }
 
+// This is an undocumented method that we need to be notified if a user clicks the close button.
+- (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
+  didDismissAlert:(id<FakeNSUserNotification>)notification
+{
+  NSString *name = [[notification userInfo] valueForKey:@"name"];
+  mOSXNC->CloseAlertCocoaString(name);
+}
+
 @end
 
 namespace mozilla {
 
+enum {
+  OSXNotificationActionDisable = 0
+};
+
 class OSXNotificationInfo {
 private:
   ~OSXNotificationInfo();
 
 public:
   NS_INLINE_DECL_REFCOUNTING(OSXNotificationInfo)
   OSXNotificationInfo(NSString *name, nsIObserver *observer,
                       const nsAString & alertCookie);
@@ -212,16 +240,43 @@ OSXNotificationCenter::ShowAlertNotifica
   Class unClass = NSClassFromString(@"NSUserNotification");
   id<FakeNSUserNotification> notification = [[unClass alloc] init];
   notification.title = [NSString stringWithCharacters:(const unichar *)aAlertTitle.BeginReading()
                                                length:aAlertTitle.Length()];
   notification.informativeText = [NSString stringWithCharacters:(const unichar *)aAlertText.BeginReading()
                                                          length:aAlertText.Length()];
   notification.soundName = NSUserNotificationDefaultSoundName;
   notification.hasActionButton = NO;
+
+  // If this is not an application/extension alert, show additional actions dealing with permissions.
+  if (!nsContentUtils::IsSystemOrExpandedPrincipal(aPrincipal)
+      && !aPrincipal->GetIsNullPrincipal()) {
+    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;
+      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));
+
+      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"];
+      [(NSObject*)notification setValue:@[
+                                          nsCocoaUtils::ToNSString(disableButtonTitle)
+                                          ]
+                               forKey:@"_alternateActionButtonTitles"];
+    }
+  }
   NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()];
   if (!alertName) {
     return NS_ERROR_FAILURE;
   }
   notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
                                                       forKeys:[NSArray arrayWithObjects:@"name", nil]];
 
   OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, aAlertCookie);
@@ -313,29 +368,46 @@ OSXNotificationCenter::CloseAlertCocoaSt
       break;
     }
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }
 
 void
-OSXNotificationCenter::OnClick(NSString *aAlertName)
+OSXNotificationCenter::OnActivate(NSString *aAlertName,
+                                  NSUserNotificationActivationType aActivationType,
+                                  unsigned long long aAdditionalActionIndex)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (!aAlertName) {
     return; // Can't do anything without a name
   }
 
   for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
     OSXNotificationInfo *osxni = mActiveAlerts[i];
     if ([aAlertName isEqualToString:osxni->mName]) {
       if (osxni->mObserver) {
-        osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
+        switch (aActivationType) {
+          case NSUserNotificationActivationTypeAdditionalActionClicked:
+          case NSUserNotificationActivationTypeActionButtonClicked:
+            switch (aAdditionalActionIndex) {
+              case OSXNotificationActionDisable:
+                osxni->mObserver->Observe(nullptr, "alertdisablecallback", osxni->mCookie.get());
+                break;
+              default:
+                NS_WARNING("Unknown NSUserNotification additional action clicked");
+                break;
+            }
+            break;
+          default:
+            osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
+            break;
+        }
       }
       return;
     }
   }
 
   NS_OBJC_END_TRY_ABORT_BLOCK;
 }