widget/cocoa/OSXNotificationCenter.mm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "OSXNotificationCenter.h"
     7 #import <AppKit/AppKit.h>
     8 #include "imgIRequest.h"
     9 #include "imgIContainer.h"
    10 #include "nsNetUtil.h"
    11 #include "imgLoader.h"
    12 #import "nsCocoaUtils.h"
    13 #include "nsObjCExceptions.h"
    14 #include "nsString.h"
    15 #include "nsCOMPtr.h"
    16 #include "nsIObserver.h"
    17 #include "imgRequestProxy.h"
    19 using namespace mozilla;
    21 #if !defined(MAC_OS_X_VERSION_10_8) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8)
    22 @protocol NSUserNotificationCenterDelegate
    23 @end
    24 static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
    25 enum {
    26   NSUserNotificationActivationTypeNone = 0,
    27   NSUserNotificationActivationTypeContentsClicked = 1,
    28   NSUserNotificationActivationTypeActionButtonClicked = 2
    29 };
    30 typedef NSInteger NSUserNotificationActivationType;
    31 #endif
    33 @protocol FakeNSUserNotification <NSObject>
    34 @property (copy) NSString* title;
    35 @property (copy) NSString* subtitle;
    36 @property (copy) NSString* informativeText;
    37 @property (copy) NSString* actionButtonTitle;
    38 @property (copy) NSDictionary* userInfo;
    39 @property (copy) NSDate* deliveryDate;
    40 @property (copy) NSTimeZone* deliveryTimeZone;
    41 @property (copy) NSDateComponents* deliveryRepeatInterval;
    42 @property (readonly) NSDate* actualDeliveryDate;
    43 @property (readonly, getter=isPresented) BOOL presented;
    44 @property (readonly, getter=isRemote) BOOL remote;
    45 @property (copy) NSString* soundName;
    46 @property BOOL hasActionButton;
    47 @property (readonly) NSUserNotificationActivationType activationType;
    48 @property (copy) NSString *otherButtonTitle;
    49 @property (copy) NSImage *contentImage;
    50 @end
    52 @protocol FakeNSUserNotificationCenter <NSObject>
    53 + (id<FakeNSUserNotificationCenter>)defaultUserNotificationCenter;
    54 @property (assign) id <NSUserNotificationCenterDelegate> delegate;
    55 @property (copy) NSArray *scheduledNotifications;
    56 - (void)scheduleNotification:(id<FakeNSUserNotification>)notification;
    57 - (void)removeScheduledNotification:(id<FakeNSUserNotification>)notification;
    58 @property (readonly) NSArray *deliveredNotifications;
    59 - (void)deliverNotification:(id<FakeNSUserNotification>)notification;
    60 - (void)removeDeliveredNotification:(id<FakeNSUserNotification>)notification;
    61 - (void)removeAllDeliveredNotifications;
    62 - (void)_removeAllDisplayedNotifications;
    63 - (void)_removeDisplayedNotification:(id<FakeNSUserNotification>)notification;
    64 @end
    66 @interface mozNotificationCenterDelegate : NSObject <NSUserNotificationCenterDelegate>
    67 {
    68   OSXNotificationCenter *mOSXNC;
    69 }
    70   - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
    71 @end
    73 @implementation mozNotificationCenterDelegate
    75 - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc
    76 {
    77   [super init];
    78   // We should *never* outlive this OSXNotificationCenter.
    79   mOSXNC = osxnc;
    80   return self;
    81 }
    83 - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
    84         didDeliverNotification:(id<FakeNSUserNotification>)notification
    85 {
    87 }
    89 - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
    90        didActivateNotification:(id<FakeNSUserNotification>)notification
    91 {
    92   mOSXNC->OnClick([[notification userInfo] valueForKey:@"name"]);
    93 }
    95 - (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
    96      shouldPresentNotification:(id<FakeNSUserNotification>)notification
    97 {
    98   return YES;
    99 }
   101 // This is an undocumented method that we need for parity with Safari.
   102 // Apple bug #15440664.
   103 - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
   104   didRemoveDeliveredNotifications:(NSArray *)notifications
   105 {
   106   for (id<FakeNSUserNotification> notification in notifications) {
   107     NSString *name = [[notification userInfo] valueForKey:@"name"];
   108     mOSXNC->CloseAlertCocoaString(name);
   109   }
   110 }
   112 @end
   114 namespace mozilla {
   116 class OSXNotificationInfo : public RefCounted<OSXNotificationInfo> {
   117 public:
   118   MOZ_DECLARE_REFCOUNTED_TYPENAME(OSXNotificationInfo)
   119   OSXNotificationInfo(NSString *name, nsIObserver *observer,
   120                       const nsAString & alertCookie);
   121   ~OSXNotificationInfo();
   123   NSString *mName;
   124   nsCOMPtr<nsIObserver> mObserver;
   125   nsString mCookie;
   126   nsRefPtr<imgRequestProxy> mIconRequest;
   127   id<FakeNSUserNotification> mPendingNotifiction;
   128   nsCOMPtr<nsITimer> mIconTimeoutTimer;
   129 };
   131 OSXNotificationInfo::OSXNotificationInfo(NSString *name, nsIObserver *observer,
   132                                          const nsAString & alertCookie)
   133 {
   134   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   136   NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!");
   137   mName = [name retain];
   138   mObserver = observer;
   139   mCookie = alertCookie;
   140   mPendingNotifiction = nil;
   142   NS_OBJC_END_TRY_ABORT_BLOCK;
   143 }
   145 OSXNotificationInfo::~OSXNotificationInfo()
   146 {
   147   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   149   [mName release];
   150   [mPendingNotifiction release];
   152   NS_OBJC_END_TRY_ABORT_BLOCK;
   153 }
   155 static id<FakeNSUserNotificationCenter> GetNotificationCenter() {
   156   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   158   Class c = NSClassFromString(@"NSUserNotificationCenter");
   159   return [c performSelector:@selector(defaultUserNotificationCenter)];
   161   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   162 }
   164 OSXNotificationCenter::OSXNotificationCenter()
   165 {
   166   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   168   mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this];
   169   GetNotificationCenter().delegate = mDelegate;
   171   NS_OBJC_END_TRY_ABORT_BLOCK;
   172 }
   174 OSXNotificationCenter::~OSXNotificationCenter()
   175 {
   176   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   178   [GetNotificationCenter() removeAllDeliveredNotifications];
   179   [mDelegate release];
   181   NS_OBJC_END_TRY_ABORT_BLOCK;
   182 }
   184 NS_IMPL_ISUPPORTS(OSXNotificationCenter, nsIAlertsService, imgINotificationObserver, nsITimerCallback)
   186 nsresult OSXNotificationCenter::Init()
   187 {
   188   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   190   return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE;
   192   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   193 }
   195 NS_IMETHODIMP
   196 OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
   197                                              const nsAString & aAlertText, bool aAlertTextClickable,
   198                                              const nsAString & aAlertCookie,
   199                                              nsIObserver * aAlertListener,
   200                                              const nsAString & aAlertName,
   201                                              const nsAString & aBidi,
   202                                              const nsAString & aLang,
   203                                              nsIPrincipal * aPrincipal)
   204 {
   205   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   207   Class unClass = NSClassFromString(@"NSUserNotification");
   208   id<FakeNSUserNotification> notification = [[unClass alloc] init];
   209   notification.title = [NSString stringWithCharacters:(const unichar *)aAlertTitle.BeginReading()
   210                                                length:aAlertTitle.Length()];
   211   notification.informativeText = [NSString stringWithCharacters:(const unichar *)aAlertText.BeginReading()
   212                                                          length:aAlertText.Length()];
   213   notification.soundName = NSUserNotificationDefaultSoundName;
   214   notification.hasActionButton = NO;
   215   NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()];
   216   if (!alertName) {
   217     return NS_ERROR_FAILURE;
   218   }
   219   notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
   220                                                       forKeys:[NSArray arrayWithObjects:@"name", nil]];
   222   OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, aAlertCookie);
   224   // Show the notification without waiting for an image if there is no icon URL or
   225   // notification icons are not supported on this version of OS X.
   226   if (aImageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) {
   227     CloseAlertCocoaString(alertName);
   228     mActiveAlerts.AppendElement(osxni);
   229     [GetNotificationCenter() deliverNotification:notification];
   230     [notification release];
   231     if (aAlertListener) {
   232       aAlertListener->Observe(nullptr, "alertshow", PromiseFlatString(aAlertCookie).get());
   233     }
   234   } else {
   235     mPendingAlerts.AppendElement(osxni);
   236     osxni->mPendingNotifiction = notification;
   237     nsRefPtr<imgLoader> il = imgLoader::GetInstance();
   238     if (il) {
   239       nsCOMPtr<nsIURI> imageUri;
   240       NS_NewURI(getter_AddRefs(imageUri), aImageUrl);
   241       if (imageUri) {
   242         nsresult rv = il->LoadImage(imageUri, nullptr, nullptr, aPrincipal, nullptr,
   243                                     this, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
   244                                     nullptr, EmptyString(),
   245                                     getter_AddRefs(osxni->mIconRequest));
   246         if (NS_SUCCEEDED(rv)) {
   247           // Set a timer for six seconds. If we don't have an icon by the time this
   248           // goes off then we go ahead without an icon.
   249           nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
   250           osxni->mIconTimeoutTimer = timer;
   251           timer->InitWithCallback(this, 6000, nsITimer::TYPE_ONE_SHOT);
   252           return NS_OK;
   253         }
   254       }
   255     }
   256     ShowPendingNotification(osxni);
   257   }
   259   return NS_OK;
   261   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   262 }
   264 NS_IMETHODIMP
   265 OSXNotificationCenter::CloseAlert(const nsAString& aAlertName,
   266                                   nsIPrincipal* aPrincipal)
   267 {
   268   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   270   NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()];
   271   CloseAlertCocoaString(alertName);
   272   return NS_OK;
   274   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   275 }
   277 void
   278 OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName)
   279 {
   280   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   282   if (!aAlertName) {
   283     return; // Can't do anything without a name
   284   }
   286   NSArray *notifications = [GetNotificationCenter() deliveredNotifications];
   287   for (id<FakeNSUserNotification> notification in notifications) {
   288     NSString *name = [[notification userInfo] valueForKey:@"name"];
   289     if ([name isEqualToString:aAlertName]) {
   290       [GetNotificationCenter() removeDeliveredNotification:notification];
   291       [GetNotificationCenter() _removeDisplayedNotification:notification];
   292       break;
   293     }
   294   }
   296   for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
   297     OSXNotificationInfo *osxni = mActiveAlerts[i];
   298     if ([aAlertName isEqualToString:osxni->mName]) {
   299       if (osxni->mObserver) {
   300         osxni->mObserver->Observe(nullptr, "alertfinished", osxni->mCookie.get());
   301       }
   302       mActiveAlerts.RemoveElementAt(i);
   303       break;
   304     }
   305   }
   307   NS_OBJC_END_TRY_ABORT_BLOCK;
   308 }
   310 void
   311 OSXNotificationCenter::OnClick(NSString *aAlertName)
   312 {
   313   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   315   if (!aAlertName) {
   316     return; // Can't do anything without a name
   317   }
   319   for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
   320     OSXNotificationInfo *osxni = mActiveAlerts[i];
   321     if ([aAlertName isEqualToString:osxni->mName]) {
   322       if (osxni->mObserver) {
   323         osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
   324       }
   325       return;
   326     }
   327   }
   329   NS_OBJC_END_TRY_ABORT_BLOCK;
   330 }
   332 void
   333 OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni)
   334 {
   335   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   337   if (osxni->mIconTimeoutTimer) {
   338     osxni->mIconTimeoutTimer->Cancel();
   339     osxni->mIconTimeoutTimer = nullptr;
   340   }
   342   if (osxni->mIconRequest) {
   343     osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
   344     osxni->mIconRequest = nullptr;
   345   }
   347   CloseAlertCocoaString(osxni->mName);
   349   for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
   350     if (mPendingAlerts[i] == osxni) {
   351       mActiveAlerts.AppendElement(osxni);
   352       mPendingAlerts.RemoveElementAt(i);
   353       break;
   354     }
   355   }
   357   [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction];
   359   if (osxni->mObserver) {
   360     osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
   361   }
   363   [osxni->mPendingNotifiction release];
   364   osxni->mPendingNotifiction = nil;
   366   NS_OBJC_END_TRY_ABORT_BLOCK;
   367 }
   369 NS_IMETHODIMP
   370 OSXNotificationCenter::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
   371 {
   372   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   374   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
   375     OSXNotificationInfo *osxni = nullptr;
   376     for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
   377       if (aRequest == mPendingAlerts[i]->mIconRequest) {
   378         osxni = mPendingAlerts[i];
   379         break;
   380       }
   381     }
   382     if (!osxni || !osxni->mPendingNotifiction) {
   383       return NS_ERROR_FAILURE;
   384     }
   385     NSImage *cocoaImage = nil;
   386     uint32_t imgStatus = imgIRequest::STATUS_ERROR;
   387     nsresult rv = aRequest->GetImageStatus(&imgStatus);
   388     if (NS_SUCCEEDED(rv) && imgStatus != imgIRequest::STATUS_ERROR) {
   389       nsCOMPtr<imgIContainer> image;
   390       rv = aRequest->GetImage(getter_AddRefs(image));
   391       if (NS_SUCCEEDED(rv)) {
   392         nsCocoaUtils::CreateNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST, &cocoaImage, 1.0f);
   393       }
   394     }
   395     (osxni->mPendingNotifiction).contentImage = cocoaImage;
   396     [cocoaImage release];
   397     ShowPendingNotification(osxni);
   398   }
   399   return NS_OK;
   401   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   402 }
   404 NS_IMETHODIMP
   405 OSXNotificationCenter::Notify(nsITimer *timer)
   406 {
   407   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
   409   if (!timer) {
   410     return NS_ERROR_FAILURE;
   411   }
   413   for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
   414     OSXNotificationInfo *osxni = mPendingAlerts[i];
   415     if (timer == osxni->mIconTimeoutTimer.get()) {
   416       osxni->mIconTimeoutTimer = nullptr;
   417       if (osxni->mPendingNotifiction) {
   418         ShowPendingNotification(osxni);
   419         break;
   420       }
   421     }
   422   }
   423   return NS_OK;
   425   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
   426 }
   428 } // namespace mozilla

mercurial