From 4e54051811d5f763d8c05644f9e3cfdb41ca739c Mon Sep 17 00:00:00 2001 From: Josh Aas Date: Tue, 3 Dec 2013 18:53:46 -0600 Subject: [PATCH] Bug 852648: Add platform-native Notification Center support for OS X. r=wchen,mstange --- toolkit/components/alerts/nsAlertsService.h | 2 +- .../components/alerts/nsIAlertsService.idl | 11 +- .../components/alerts/test/test_alerts.html | 9 +- widget/cocoa/OSXNotificationCenter.h | 48 ++ widget/cocoa/OSXNotificationCenter.mm | 432 ++++++++++++++++++ widget/cocoa/moz.build | 1 + widget/cocoa/nsWidgetFactory.mm | 6 + 7 files changed, 503 insertions(+), 6 deletions(-) create mode 100644 widget/cocoa/OSXNotificationCenter.h create mode 100644 widget/cocoa/OSXNotificationCenter.mm diff --git a/toolkit/components/alerts/nsAlertsService.h b/toolkit/components/alerts/nsAlertsService.h index e825a5acf895..c0f228417e5e 100644 --- a/toolkit/components/alerts/nsAlertsService.h +++ b/toolkit/components/alerts/nsAlertsService.h @@ -33,7 +33,7 @@ class nsAlertsService : public nsIAlertsService, public: NS_DECL_NSIALERTSPROGRESSLISTENER NS_DECL_NSIALERTSSERVICE - NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_ISUPPORTS nsAlertsService(); virtual ~nsAlertsService(); diff --git a/toolkit/components/alerts/nsIAlertsService.idl b/toolkit/components/alerts/nsIAlertsService.idl index 680669322817..1e8e91f510de 100644 --- a/toolkit/components/alerts/nsIAlertsService.idl +++ b/toolkit/components/alerts/nsIAlertsService.idl @@ -16,6 +16,9 @@ interface nsIAlertsService : nsISupports * Displays a sliding notification window. * * @param imageUrl A URL identifying the image to put in the alert. + * The OS X implemenation limits the amount of time it + * will wait for an icon to load to six seconds. After + * that time the alert will show with no icon. * @param title The title for the alert. * @param text The contents of the alert. * @param textClickable If true, causes the alert text to look like a link @@ -25,10 +28,10 @@ interface nsIAlertsService : nsISupports * consumer during the alert listener callbacks. * @param alertListener Used for callbacks. May be null if the caller * doesn't care about callbacks. - * @param name The name of the notification. This is currently - * only used on Android. On Android the name is hashed - * and used as a notification ID. Notifications will - * replace previous notifications with the same name. + * @param name The name of the notification. This is currently only + * used on Android and OS X. On Android the name is + * hashed and used as a notification ID. Notifications + * will replace previous notifications with the same name. * @param dir Bidi override for the title. Valid values are * "auto", "ltr" or "rtl". Only available on supported * platforms. diff --git a/toolkit/components/alerts/test/test_alerts.html b/toolkit/components/alerts/test/test_alerts.html index 431f0d76749b..d4dc2e494c6d 100644 --- a/toolkit/components/alerts/test/test_alerts.html +++ b/toolkit/components/alerts/test/test_alerts.html @@ -58,11 +58,18 @@ function runTest() { } try { + var alertName = "fiorello"; SimpleTest.waitForExplicitFinish(); notifier.showAlertNotification(null, "Notification test", "Surprise! I'm here to test notifications!", - false, "foobarcookie", observer); + false, "foobarcookie", observer, alertname); ok(true, "showAlertNotification() succeeded. Waiting for notification..."); + if (MAC) { + // Notifications are native on OS X 10.8 and later, and when they are they + // persist in the Notification Center. We need to close explicitly to avoid a hang. + // This also works for XUL notifications when running this test on OS X < 10.8. + notifier.closeAlert(alertName); + } } catch (ex) { todo(false, "showAlertNotification() failed.", ex); SimpleTest.finish(); diff --git a/widget/cocoa/OSXNotificationCenter.h b/widget/cocoa/OSXNotificationCenter.h new file mode 100644 index 000000000000..2eba27e02b35 --- /dev/null +++ b/widget/cocoa/OSXNotificationCenter.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 +#include "nsIAlertsService.h" +#include "imgINotificationObserver.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "mozilla/RefPtr.h" + +@class mozNotificationCenterDelegate; + +namespace mozilla { + +class OSXNotificationInfo; + +class OSXNotificationCenter : public nsIAlertsService, + public imgINotificationObserver, + public nsITimerCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIALERTSSERVICE + NS_DECL_IMGINOTIFICATIONOBSERVER + NS_DECL_NSITIMERCALLBACK + + OSXNotificationCenter(); + virtual ~OSXNotificationCenter(); + + nsresult Init(); + void CloseAlertCocoaString(NSString *aAlertName); + void OnClick(NSString *aAlertName); + void ShowPendingNotification(OSXNotificationInfo *osxni); + +private: + mozNotificationCenterDelegate *mDelegate; + nsTArray > mActiveAlerts; + nsTArray > mPendingAlerts; +}; + +} // namespace mozilla + +#endif // OSXNotificationCenter_h diff --git a/widget/cocoa/OSXNotificationCenter.mm b/widget/cocoa/OSXNotificationCenter.mm new file mode 100644 index 000000000000..a22994297880 --- /dev/null +++ b/widget/cocoa/OSXNotificationCenter.mm @@ -0,0 +1,432 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 +#include "imgIRequest.h" +#include "imgIContainer.h" +#include "nsNetUtil.h" +#include "imgLoader.h" +#import "nsCocoaUtils.h" +#include "nsObjCExceptions.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "imgRequestProxy.h" + +#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 +}; +typedef NSInteger NSUserNotificationActivationType; +#endif + +@protocol FakeNSUserNotification +@property (copy) NSString* title; +@property (copy) NSString* subtitle; +@property (copy) NSString* informativeText; +@property (copy) NSString* actionButtonTitle; +@property (copy) NSDictionary* userInfo; +@property (copy) NSDate* deliveryDate; +@property (copy) NSTimeZone* deliveryTimeZone; +@property (copy) NSDateComponents* deliveryRepeatInterval; +@property (readonly) NSDate* actualDeliveryDate; +@property (readonly, getter=isPresented) BOOL presented; +@property (readonly, getter=isRemote) BOOL remote; +@property (copy) NSString* soundName; +@property BOOL hasActionButton; +@property (readonly) NSUserNotificationActivationType activationType; +@property (copy) NSString *otherButtonTitle; +@property (copy) NSImage *contentImage; +@end + +@protocol FakeNSUserNotificationCenter ++ (id)defaultUserNotificationCenter; +@property (assign) id delegate; +@property (copy) NSArray *scheduledNotifications; +- (void)scheduleNotification:(id)notification; +- (void)removeScheduledNotification:(id)notification; +@property (readonly) NSArray *deliveredNotifications; +- (void)deliverNotification:(id)notification; +- (void)removeDeliveredNotification:(id)notification; +- (void)removeAllDeliveredNotifications; +- (void)_removeAllDisplayedNotifications; +- (void)_removeDisplayedNotification:(id)notification; +@end + +@interface mozNotificationCenterDelegate : NSObject +{ + OSXNotificationCenter *mOSXNC; +} + - (id)initWithOSXNC:(OSXNotificationCenter*)osxnc; +@end + +@implementation mozNotificationCenterDelegate + +- (id)initWithOSXNC:(OSXNotificationCenter*)osxnc +{ + [super init]; + // We should *never* outlive this OSXNotificationCenter. + mOSXNC = osxnc; + return self; +} + +- (void)userNotificationCenter:(id)center + didDeliverNotification:(id)notification +{ + +} + +- (void)userNotificationCenter:(id)center + didActivateNotification:(id)notification +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + mOSXNC->OnClick([[notification userInfo] valueForKey:@"name"]); + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (BOOL)userNotificationCenter:(id)center + shouldPresentNotification:(id)notification +{ + return YES; +} + +// This is an undocumented method that we need for parity with Safari. +// Apple bug #15440664. +- (void)userNotificationCenter:(id)center + didRemoveDeliveredNotifications:(NSArray *)notifications +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + for (id notification in notifications) { + NSString *name = [[notification userInfo] valueForKey:@"name"]; + mOSXNC->CloseAlertCocoaString(name); + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +@end + +namespace mozilla { + +class OSXNotificationInfo : public RefCounted { +public: + OSXNotificationInfo(NSString *name, nsIObserver *observer, + const nsAString & alertCookie); + ~OSXNotificationInfo(); + + NSString *mName; + nsCOMPtr mObserver; + nsString mCookie; + nsRefPtr mIconRequest; + id mPendingNotifiction; + nsCOMPtr mIconTimeoutTimer; +}; + +OSXNotificationInfo::OSXNotificationInfo(NSString *name, nsIObserver *observer, + const nsAString & alertCookie) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NS_ASSERTION(name, "Cannot create OSXNotificationInfo without a name!"); + mName = [name retain]; + mObserver = observer; + mCookie = alertCookie; + mPendingNotifiction = nil; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +OSXNotificationInfo::~OSXNotificationInfo() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [mName release]; + [mPendingNotifiction release]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +static id GetNotificationCenter() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + Class c = NSClassFromString(@"NSUserNotificationCenter"); + return [c performSelector:@selector(defaultUserNotificationCenter)]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +OSXNotificationCenter::OSXNotificationCenter() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + mDelegate = [[mozNotificationCenterDelegate alloc] initWithOSXNC:this]; + GetNotificationCenter().delegate = mDelegate; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +OSXNotificationCenter::~OSXNotificationCenter() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [GetNotificationCenter() removeAllDeliveredNotifications]; + [mDelegate release]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +NS_IMPL_ISUPPORTS3(OSXNotificationCenter, nsIAlertsService, imgINotificationObserver, nsITimerCallback) + +nsresult OSXNotificationCenter::Init() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK : NS_ERROR_FAILURE; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +OSXNotificationCenter::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle, + const nsAString & aAlertText, bool aAlertTextClickable, + const nsAString & aAlertCookie, + nsIObserver * aAlertListener, + const nsAString & aAlertName, + const nsAString & aBidi, + const nsAString & aLang, + nsIPrincipal * aPrincipal) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + Class unClass = NSClassFromString(@"NSUserNotification"); + id 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; + 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); + + // Show the notification without waiting for an image if there is no icon URL or + // notification icons are not supported on this version of OS X. + if (aImageUrl.IsEmpty() || ![unClass instancesRespondToSelector:@selector(setContentImage:)]) { + CloseAlertCocoaString(alertName); + mActiveAlerts.AppendElement(osxni); + [GetNotificationCenter() deliverNotification:notification]; + [notification release]; + if (aAlertListener) { + aAlertListener->Observe(nullptr, "alertshow", PromiseFlatString(aAlertCookie).get()); + } + } else { + mPendingAlerts.AppendElement(osxni); + osxni->mPendingNotifiction = notification; + nsRefPtr il = imgLoader::GetInstance(); + if (il) { + nsCOMPtr imageUri; + NS_NewURI(getter_AddRefs(imageUri), aImageUrl); + if (imageUri) { + nsresult rv = il->LoadImage(imageUri, nullptr, nullptr, aPrincipal, nullptr, + this, nullptr, nsIRequest::LOAD_NORMAL, nullptr, + nullptr, getter_AddRefs(osxni->mIconRequest)); + if (NS_SUCCEEDED(rv)) { + // Set a timer for six seconds. If we don't have an icon by the time this + // goes off then we go ahead without an icon. + nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); + osxni->mIconTimeoutTimer = timer; + timer->InitWithCallback(this, 6000, nsITimer::TYPE_ONE_SHOT); + return NS_OK; + } + } + } + ShowPendingNotification(osxni); + } + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +OSXNotificationCenter::CloseAlert(const nsAString& aAlertName, + nsIPrincipal* aPrincipal) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NSString *alertName = [NSString stringWithCharacters:(const unichar *)aAlertName.BeginReading() length:aAlertName.Length()]; + CloseAlertCocoaString(alertName); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +void +OSXNotificationCenter::CloseAlertCocoaString(NSString *aAlertName) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + if (!aAlertName) { + return; // Can't do anything without a name + } + + NSArray *notifications = [GetNotificationCenter() deliveredNotifications]; + for (id notification in notifications) { + NSString *name = [[notification userInfo] valueForKey:@"name"]; + if ([name isEqualToString:aAlertName]) { + [GetNotificationCenter() removeDeliveredNotification:notification]; + [GetNotificationCenter() _removeDisplayedNotification:notification]; + break; + } + } + + 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, "alertfinished", osxni->mCookie.get()); + } + mActiveAlerts.RemoveElementAt(i); + break; + } + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +void +OSXNotificationCenter::OnClick(NSString *aAlertName) +{ + 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()); + } + return; + } + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +void +OSXNotificationCenter::ShowPendingNotification(OSXNotificationInfo *osxni) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + if (osxni->mIconTimeoutTimer) { + osxni->mIconTimeoutTimer->Cancel(); + osxni->mIconTimeoutTimer = nullptr; + } + + if (osxni->mIconRequest) { + osxni->mIconRequest->Cancel(NS_BINDING_ABORTED); + osxni->mIconRequest = nullptr; + } + + CloseAlertCocoaString(osxni->mName); + + for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) { + if (mPendingAlerts[i] == osxni) { + mActiveAlerts.AppendElement(osxni); + mPendingAlerts.RemoveElementAt(i); + break; + } + } + + [GetNotificationCenter() deliverNotification:osxni->mPendingNotifiction]; + + if (osxni->mObserver) { + osxni->mObserver->Observe(nullptr, "alertshow", osxni->mCookie.get()); + } + + [osxni->mPendingNotifiction release]; + osxni->mPendingNotifiction = nil; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +NS_IMETHODIMP +OSXNotificationCenter::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + OSXNotificationInfo *osxni = nullptr; + for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) { + if (aRequest == mPendingAlerts[i]->mIconRequest) { + osxni = mPendingAlerts[i]; + break; + } + } + if (!osxni || !osxni->mPendingNotifiction) { + return NS_ERROR_FAILURE; + } + NSImage *cocoaImage = nil; + uint32_t imgStatus = imgIRequest::STATUS_ERROR; + nsresult rv = aRequest->GetImageStatus(&imgStatus); + if (NS_SUCCEEDED(rv) && imgStatus != imgIRequest::STATUS_ERROR) { + nsCOMPtr image; + rv = aRequest->GetImage(getter_AddRefs(image)); + if (NS_SUCCEEDED(rv)) { + nsCocoaUtils::CreateNSImageFromImageContainer(image, imgIContainer::FRAME_FIRST, &cocoaImage); + } + } + (osxni->mPendingNotifiction).contentImage = cocoaImage; + [cocoaImage release]; + ShowPendingNotification(osxni); + } + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +OSXNotificationCenter::Notify(nsITimer *timer) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (!timer) { + return NS_ERROR_FAILURE; + } + + for (unsigned int i = 0; i < mPendingAlerts.Length(); i++) { + OSXNotificationInfo *osxni = mPendingAlerts[i]; + if (timer == osxni->mIconTimeoutTimer.get()) { + osxni->mIconTimeoutTimer = nullptr; + if (osxni->mPendingNotifiction) { + ShowPendingNotification(osxni); + break; + } + } + } + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +} // namespace mozilla diff --git a/widget/cocoa/moz.build b/widget/cocoa/moz.build index bd4caa5fd447..7988cca3815f 100644 --- a/widget/cocoa/moz.build +++ b/widget/cocoa/moz.build @@ -53,6 +53,7 @@ UNIFIED_SOURCES += [ 'nsToolkit.mm', 'nsWidgetFactory.mm', 'nsWindowMap.mm', + 'OSXNotificationCenter.mm', 'WidgetTraceEvent.mm', ] diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm index c4c072381566..e986e3e57036 100644 --- a/widget/cocoa/nsWidgetFactory.mm +++ b/widget/cocoa/nsWidgetFactory.mm @@ -28,12 +28,14 @@ #include "nsSound.h" #include "nsIdleServiceX.h" +#include "OSXNotificationCenter.h" #include "nsScreenManagerCocoa.h" #include "nsDeviceContextSpecX.h" #include "nsPrintOptionsX.h" #include "nsPrintDialogX.h" #include "nsPrintSession.h" +#include "nsToolkitCompsCID.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsCocoaWindow) NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildView) @@ -51,6 +53,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintOptionsX, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintDialogServiceX, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintSession, Init) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceX, nsIdleServiceX::GetInstance) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(OSXNotificationCenter, Init) #include "nsMenuBarX.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsNativeMenuServiceX) @@ -151,6 +154,7 @@ NS_DEFINE_NAMED_CID(NS_PRINTSESSION_CID); NS_DEFINE_NAMED_CID(NS_PRINTSETTINGSSERVICE_CID); NS_DEFINE_NAMED_CID(NS_PRINTDIALOGSERVICE_CID); NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID); +NS_DEFINE_NAMED_CID(NS_SYSTEMALERTSSERVICE_CID); NS_DEFINE_NAMED_CID(NS_NATIVEMENUSERVICE_CID); NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID); NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID); @@ -182,6 +186,7 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = { { &kNS_PRINTSETTINGSSERVICE_CID, false, NULL, nsPrintOptionsXConstructor }, { &kNS_PRINTDIALOGSERVICE_CID, false, NULL, nsPrintDialogServiceXConstructor }, { &kNS_IDLE_SERVICE_CID, false, NULL, nsIdleServiceXConstructor }, + { &kNS_SYSTEMALERTSSERVICE_CID, false, NULL, OSXNotificationCenterConstructor }, { &kNS_NATIVEMENUSERVICE_CID, false, NULL, nsNativeMenuServiceXConstructor }, { &kNS_MACDOCKSUPPORT_CID, false, NULL, nsMacDockSupportConstructor }, { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor }, @@ -217,6 +222,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { { "@mozilla.org/gfx/printsettings-service;1", &kNS_PRINTSETTINGSSERVICE_CID }, { NS_PRINTDIALOGSERVICE_CONTRACTID, &kNS_PRINTDIALOGSERVICE_CID }, { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID }, + { "@mozilla.org/system-alerts-service;1", &kNS_SYSTEMALERTSSERVICE_CID }, { "@mozilla.org/widget/nativemenuservice;1", &kNS_NATIVEMENUSERVICE_CID }, { "@mozilla.org/widget/macdocksupport;1", &kNS_MACDOCKSUPPORT_CID }, { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID },