Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / widget / cocoa / OSXNotificationCenter.mm
blob43e44db0d6fc676e9fbff0db9698ffa062e739c2
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
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>
68   OSXNotificationCenter *mOSXNC;
70   - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
71 @end
73 @implementation mozNotificationCenterDelegate
75 - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc
77   [super init];
78   // We should *never* outlive this OSXNotificationCenter.
79   mOSXNC = osxnc;
80   return self;
83 - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
84         didDeliverNotification:(id<FakeNSUserNotification>)notification
89 - (void)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
90        didActivateNotification:(id<FakeNSUserNotification>)notification
92   mOSXNC->OnClick([[notification userInfo] valueForKey:@"name"]);
95 - (BOOL)userNotificationCenter:(id<FakeNSUserNotificationCenter>)center
96      shouldPresentNotification:(id<FakeNSUserNotification>)notification
98   return YES;
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
106   for (id<FakeNSUserNotification> notification in notifications) {
107     NSString *name = [[notification userInfo] valueForKey:@"name"];
108     mOSXNC->CloseAlertCocoaString(name);
109   }
112 @end
114 namespace mozilla {
116 class OSXNotificationInfo {
117 private:
118   ~OSXNotificationInfo();
120 public:
121   NS_INLINE_DECL_REFCOUNTING(OSXNotificationInfo)
122   OSXNotificationInfo(NSString *name, nsIObserver *observer,
123                       const nsAString & alertCookie);
125   NSString *mName;
126   nsCOMPtr<nsIObserver> mObserver;
127   nsString mCookie;
128   nsRefPtr<imgRequestProxy> mIconRequest;
129   id<FakeNSUserNotification> mPendingNotifiction;
130   nsCOMPtr<nsITimer> mIconTimeoutTimer;
133 OSXNotificationInfo::OSXNotificationInfo(NSString *name, nsIObserver *observer,
134                                          const nsAString & alertCookie)
136   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
138   NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!");
139   mName = [name retain];
140   mObserver = observer;
141   mCookie = alertCookie;
142   mPendingNotifiction = nil;
144   NS_OBJC_END_TRY_ABORT_BLOCK;
147 OSXNotificationInfo::~OSXNotificationInfo()
149   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
151   [mName release];
152   [mPendingNotifiction release];
154   NS_OBJC_END_TRY_ABORT_BLOCK;
157 static id<FakeNSUserNotificationCenter> GetNotificationCenter() {
158   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
160   Class c = NSClassFromString(@"NSUserNotificationCenter");
161   return [c performSelector:@selector(defaultUserNotificationCenter)];
163   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
166 OSXNotificationCenter::OSXNotificationCenter()
168   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
170   mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this];
171   GetNotificationCenter().delegate = mDelegate;
173   NS_OBJC_END_TRY_ABORT_BLOCK;
176 OSXNotificationCenter::~OSXNotificationCenter()
178   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
180   [GetNotificationCenter() removeAllDeliveredNotifications];
181   [mDelegate release];
183   NS_OBJC_END_TRY_ABORT_BLOCK;
186 NS_IMPL_ISUPPORTS(OSXNotificationCenter, nsIAlertsService, imgINotificationObserver, nsITimerCallback)
188 nsresult OSXNotificationCenter::Init()
190   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
192   return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE;
194   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
197 NS_IMETHODIMP
198 OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
199                                              const nsAString & aAlertText, bool aAlertTextClickable,
200                                              const nsAString & aAlertCookie,
201                                              nsIObserver * aAlertListener,
202                                              const nsAString & aAlertName,
203                                              const nsAString & aBidi,
204                                              const nsAString & aLang,
205                                              const nsAString & aData,
206                                              nsIPrincipal * aPrincipal)
208   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
210   Class unClass = NSClassFromString(@"NSUserNotification");
211   id<FakeNSUserNotification> notification = [[unClass alloc] init];
212   notification.title = [NSString stringWithCharacters:(const unichar *)aAlertTitle.BeginReading()
213                                                length:aAlertTitle.Length()];
214   notification.informativeText = [NSString stringWithCharacters:(const unichar *)aAlertText.BeginReading()
215                                                          length:aAlertText.Length()];
216   notification.soundName = NSUserNotificationDefaultSoundName;
217   notification.hasActionButton = NO;
218   NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()];
219   if (!alertName) {
220     return NS_ERROR_FAILURE;
221   }
222   notification.userInfo = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
223                                                       forKeys:[NSArray arrayWithObjects:@"name", nil]];
225   OSXNotificationInfo *osxni = new OSXNotificationInfo(alertName, aAlertListener, aAlertCookie);
227   // Show the notification without waiting for an image if there is no icon URL or
228   // notification icons are not supported on this version of OS X.
229   if (aImageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) {
230     CloseAlertCocoaString(alertName);
231     mActiveAlerts.AppendElement(osxni);
232     [GetNotificationCenter() deliverNotification:notification];
233     [notification release];
234     if (aAlertListener) {
235       aAlertListener->Observe(nullptr, "alertshow", PromiseFlatString(aAlertCookie).get());
236     }
237   } else {
238     mPendingAlerts.AppendElement(osxni);
239     osxni->mPendingNotifiction = notification;
240     nsRefPtr<imgLoader> il = imgLoader::GetInstance();
241     if (il) {
242       nsCOMPtr<nsIURI> imageUri;
243       NS_NewURI(getter_AddRefs(imageUri), aImageUrl);
244       if (imageUri) {
245         nsresult rv = il->LoadImage(imageUri, nullptr, nullptr, aPrincipal, nullptr,
246                                     this, nullptr, nsIRequest::LOAD_NORMAL, nullptr,
247                                     nullptr, EmptyString(),
248                                     getter_AddRefs(osxni->mIconRequest));
249         if (NS_SUCCEEDED(rv)) {
250           // Set a timer for six seconds. If we don't have an icon by the time this
251           // goes off then we go ahead without an icon.
252           nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
253           osxni->mIconTimeoutTimer = timer;
254           timer->InitWithCallback(this, 6000, nsITimer::TYPE_ONE_SHOT);
255           return NS_OK;
256         }
257       }
258     }
259     ShowPendingNotification(osxni);
260   }
262   return NS_OK;
264   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
267 NS_IMETHODIMP
268 OSXNotificationCenter::CloseAlert(const nsAString& aAlertName,
269                                   nsIPrincipal* aPrincipal)
271   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
273   NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()];
274   CloseAlertCocoaString(alertName);
275   return NS_OK;
277   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
280 void
281 OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName)
283   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
285   if (!aAlertName) {
286     return; // Can't do anything without a name
287   }
289   NSArray *notifications = [GetNotificationCenter() deliveredNotifications];
290   for (id<FakeNSUserNotification> notification in notifications) {
291     NSString *name = [[notification userInfo] valueForKey:@"name"];
292     if ([name isEqualToString:aAlertName]) {
293       [GetNotificationCenter() removeDeliveredNotification:notification];
294       [GetNotificationCenter() _removeDisplayedNotification:notification];
295       break;
296     }
297   }
299   for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
300     OSXNotificationInfo *osxni = mActiveAlerts[i];
301     if ([aAlertName isEqualToString:osxni->mName]) {
302       if (osxni->mObserver) {
303         osxni->mObserver->Observe(nullptr, "alertfinished", osxni->mCookie.get());
304       }
305       mActiveAlerts.RemoveElementAt(i);
306       break;
307     }
308   }
310   NS_OBJC_END_TRY_ABORT_BLOCK;
313 void
314 OSXNotificationCenter::OnClick(NSString *aAlertName)
316   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
318   if (!aAlertName) {
319     return; // Can't do anything without a name
320   }
322   for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
323     OSXNotificationInfo *osxni = mActiveAlerts[i];
324     if ([aAlertName isEqualToString:osxni->mName]) {
325       if (osxni->mObserver) {
326         osxni->mObserver->Observe(nullptr, "alertclickcallback", osxni->mCookie.get());
327       }
328       return;
329     }
330   }
332   NS_OBJC_END_TRY_ABORT_BLOCK;
335 void
336 OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni)
338   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
340   if (osxni->mIconTimeoutTimer) {
341     osxni->mIconTimeoutTimer->Cancel();
342     osxni->mIconTimeoutTimer = nullptr;
343   }
345   if (osxni->mIconRequest) {
346     osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
347     osxni->mIconRequest = nullptr;
348   }
350   CloseAlertCocoaString(osxni->mName);
352   for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
353     if (mPendingAlerts[i] == osxni) {
354       mActiveAlerts.AppendElement(osxni);
355       mPendingAlerts.RemoveElementAt(i);
356       break;
357     }
358   }
360   [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction];
362   if (osxni->mObserver) {
363     osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
364   }
366   [osxni->mPendingNotifiction release];
367   osxni->mPendingNotifiction = nil;
369   NS_OBJC_END_TRY_ABORT_BLOCK;
372 NS_IMETHODIMP
373 OSXNotificationCenter::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData)
375   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
377   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
378     OSXNotificationInfo *osxni = nullptr;
379     for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
380       if (aRequest == mPendingAlerts[i]->mIconRequest) {
381         osxni = mPendingAlerts[i];
382         break;
383       }
384     }
385     if (!osxni || !osxni->mPendingNotifiction) {
386       return NS_ERROR_FAILURE;
387     }
388     NSImage *cocoaImage = nil;
389     uint32_t imgStatus = imgIRequest::STATUS_ERROR;
390     nsresult rv = aRequest->GetImageStatus(&imgStatus);
391     if (NS_SUCCEEDED(rv) && imgStatus != imgIRequest::STATUS_ERROR) {
392       nsCOMPtr<imgIContainer> image;
393       rv = aRequest->GetImage(getter_AddRefs(image));
394       if (NS_SUCCEEDED(rv)) {
395         nsCocoaUtils::CreateNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST, &cocoaImage, 1.0f);
396       }
397     }
398     (osxni->mPendingNotifiction).contentImage = cocoaImage;
399     [cocoaImage release];
400     ShowPendingNotification(osxni);
401   }
402   return NS_OK;
404   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
407 NS_IMETHODIMP
408 OSXNotificationCenter::Notify(nsITimer *timer)
410   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
412   if (!timer) {
413     return NS_ERROR_FAILURE;
414   }
416   for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) {
417     OSXNotificationInfo *osxni = mPendingAlerts[i];
418     if (timer == osxni->mIconTimeoutTimer.get()) {
419       osxni->mIconTimeoutTimer = nullptr;
420       if (osxni->mPendingNotifiction) {
421         ShowPendingNotification(osxni);
422         break;
423       }
424     }
425   }
426   return NS_OK;
428   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
431 } // namespace mozilla