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"
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
24 static NSString * const NSUserNotificationDefaultSoundName = @"DefaultSoundName";
26 NSUserNotificationActivationTypeNone = 0,
27 NSUserNotificationActivationTypeContentsClicked = 1,
28 NSUserNotificationActivationTypeActionButtonClicked = 2
30 typedef NSInteger NSUserNotificationActivationType;
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;
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;
66 @interface mozNotificationCenterDelegate : NSObject <NSUserNotificationCenterDelegate>
68 OSXNotificationCenter *mOSXNC;
70 - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc;
73 @implementation mozNotificationCenterDelegate
75 - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc
78 // We should *never* outlive this OSXNotificationCenter.
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
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);
116 class OSXNotificationInfo {
118 ~OSXNotificationInfo();
121 NS_INLINE_DECL_REFCOUNTING(OSXNotificationInfo)
122 OSXNotificationInfo(NSString *name, nsIObserver *observer,
123 const nsAString & alertCookie);
126 nsCOMPtr<nsIObserver> mObserver;
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;
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];
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;
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()];
220 return NS_ERROR_FAILURE;
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());
238 mPendingAlerts.AppendElement(osxni);
239 osxni->mPendingNotifiction = notification;
240 nsRefPtr<imgLoader> il = imgLoader::GetInstance();
242 nsCOMPtr<nsIURI> imageUri;
243 NS_NewURI(getter_AddRefs(imageUri), aImageUrl);
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);
259 ShowPendingNotification(osxni);
264 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
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);
277 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
281 OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName)
283 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
286 return; // Can't do anything without a name
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];
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());
305 mActiveAlerts.RemoveElementAt(i);
310 NS_OBJC_END_TRY_ABORT_BLOCK;
314 OSXNotificationCenter::OnClick(NSString *aAlertName)
316 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
319 return; // Can't do anything without a name
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());
332 NS_OBJC_END_TRY_ABORT_BLOCK;
336 OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni)
338 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
340 if (osxni->mIconTimeoutTimer) {
341 osxni->mIconTimeoutTimer->Cancel();
342 osxni->mIconTimeoutTimer = nullptr;
345 if (osxni->mIconRequest) {
346 osxni->mIconRequest->Cancel(NS_BINDING_ABORTED);
347 osxni->mIconRequest = nullptr;
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);
360 [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction];
362 if (osxni->mObserver) {
363 osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get());
366 [osxni->mPendingNotifiction release];
367 osxni->mPendingNotifiction = nil;
369 NS_OBJC_END_TRY_ABORT_BLOCK;
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];
385 if (!osxni || !osxni->mPendingNotifiction) {
386 return NS_ERROR_FAILURE;
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);
398 (osxni->mPendingNotifiction).contentImage = cocoaImage;
399 [cocoaImage release];
400 ShowPendingNotification(osxni);
404 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
408 OSXNotificationCenter::Notify(nsITimer *timer)
410 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
413 return NS_ERROR_FAILURE;
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);
428 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
431 } // namespace mozilla