widget/cocoa/OSXNotificationCenter.mm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial