Autogenerated with:
cp dom/.clang-format widget/ && mach format widget/**.{cpp,h,mm}
Manual changes:
* Had to add a manual override in nsWaylandDisplay.h /
DMABufFormats.cpp to include mozwayland before protocols.
* Missing includes in nsWindowWayland/X11.h / nsDragService.
* nsFilePicker missed an nsWindow forward declaration.
* Various overrides in widget/windows because windows headers are order
dependent.
Differential Revision: https://phabricator.services.mozilla.com/D311688
499 lines
16 KiB
Plaintext
499 lines
16 KiB
Plaintext
/* 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 "MacOSNotificationCenter.h"
|
|
#import <AppKit/AppKit.h>
|
|
#include "imgIContainer.h"
|
|
#include "imgIRequest.h"
|
|
#include "nsCOMPtr.h"
|
|
#import "nsCocoaUtils.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsICancelable.h"
|
|
#include "nsIObserver.h"
|
|
#include "nsIStringBundle.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsObjCExceptions.h"
|
|
#include "nsString.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
#define MAX_NOTIFICATION_NAME_LEN 5000
|
|
|
|
static constexpr nsLiteralString kActionSuffix = u"-moz"_ns;
|
|
|
|
@interface mozNotificationCenterDelegate
|
|
: NSObject <NSUserNotificationCenterDelegate> {
|
|
MacOSNotificationCenter* mMacOSNC;
|
|
}
|
|
- (id)initWithMacOSNC:(MacOSNotificationCenter*)macosnc;
|
|
@end
|
|
|
|
@implementation mozNotificationCenterDelegate
|
|
|
|
- (id)initWithMacOSNC:(MacOSNotificationCenter*)macosnc {
|
|
[super init];
|
|
// We should *never* outlive this MacOSNotificationCenter.
|
|
mMacOSNC = macosnc;
|
|
return self;
|
|
}
|
|
|
|
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
|
|
didDeliverNotification:(NSUserNotification*)notification {
|
|
}
|
|
|
|
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
|
|
didActivateNotification:(NSUserNotification*)notification {
|
|
mMacOSNC->OnActivate([[notification userInfo] valueForKey:@"name"],
|
|
notification.activationType,
|
|
notification.additionalActivationAction);
|
|
}
|
|
|
|
- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center
|
|
shouldPresentNotification:(NSUserNotification*)notification {
|
|
return YES;
|
|
}
|
|
|
|
// This is an undocumented method that we need for parity with Safari.
|
|
// Apple bug #15440664.
|
|
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
|
|
didRemoveDeliveredNotifications:(NSArray*)notifications {
|
|
for (NSUserNotification* notification in notifications) {
|
|
NSString* name = [[notification userInfo] valueForKey:@"name"];
|
|
mMacOSNC->CloseAlertCocoaString(name);
|
|
}
|
|
}
|
|
|
|
// This is an undocumented method that we need to be notified if a user clicks
|
|
// the close button.
|
|
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
|
|
didDismissAlert:(NSUserNotification*)notification {
|
|
NSString* name = [[notification userInfo] valueForKey:@"name"];
|
|
mMacOSNC->CloseAlertCocoaString(name);
|
|
}
|
|
|
|
@end
|
|
|
|
namespace mozilla {
|
|
|
|
class MacOSNotificationInfo final : public nsISupports {
|
|
private:
|
|
virtual ~MacOSNotificationInfo();
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
MacOSNotificationInfo(NSString* name,
|
|
nsIAlertNotification* aAlertNotification,
|
|
nsIObserver* observer, const nsAString& alertCookie,
|
|
bool privateBrowsing);
|
|
|
|
NSString* mName;
|
|
nsCOMPtr<nsIAlertNotification> mAlertNotification;
|
|
nsCOMPtr<nsIObserver> mObserver;
|
|
nsString mCookie;
|
|
bool mPrivateBrowsing;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS0(MacOSNotificationInfo)
|
|
|
|
MacOSNotificationInfo::MacOSNotificationInfo(
|
|
NSString* name, nsIAlertNotification* aAlertNotification,
|
|
nsIObserver* observer, const nsAString& alertCookie, bool privateBrowsing) {
|
|
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
|
|
|
NS_ASSERTION(name, "Cannot create MacOSNotificationInfo without a name!");
|
|
mName = [name retain];
|
|
mAlertNotification = aAlertNotification;
|
|
mObserver = observer;
|
|
mCookie = alertCookie;
|
|
mPrivateBrowsing = privateBrowsing;
|
|
|
|
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
|
}
|
|
|
|
MacOSNotificationInfo::~MacOSNotificationInfo() {
|
|
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
|
|
|
[mName release];
|
|
|
|
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
|
}
|
|
|
|
static NSUserNotificationCenter* GetNotificationCenter() {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
|
|
|
|
Class c = NSClassFromString(@"NSUserNotificationCenter");
|
|
return [c performSelector:@selector(defaultUserNotificationCenter)];
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(nil);
|
|
}
|
|
|
|
MacOSNotificationCenter::MacOSNotificationCenter() {
|
|
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
|
|
|
mDelegate = [[mozNotificationCenterDelegate alloc] initWithMacOSNC:this];
|
|
GetNotificationCenter().delegate = mDelegate;
|
|
mSuppressForScreenSharing = false;
|
|
|
|
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
|
}
|
|
|
|
MacOSNotificationCenter::~MacOSNotificationCenter() {
|
|
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
|
|
|
[GetNotificationCenter() removeAllDeliveredNotifications];
|
|
[mDelegate release];
|
|
|
|
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(MacOSNotificationCenter, nsIAlertsService,
|
|
nsIAlertsDoNotDisturb)
|
|
|
|
nsresult MacOSNotificationCenter::Init() {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
|
|
|
|
return (!!NSClassFromString(@"NSUserNotification")) ? NS_OK
|
|
: NS_ERROR_FAILURE;
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::ShowAlert(nsIAlertNotification* aAlert,
|
|
nsIObserver* aAlertListener) {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
|
|
|
|
NS_ENSURE_ARG(aAlert);
|
|
|
|
if (mSuppressForScreenSharing) {
|
|
return NS_OK;
|
|
}
|
|
|
|
Class unClass = NSClassFromString(@"NSUserNotification");
|
|
NSUserNotification* notification = [[[unClass alloc] init] autorelease];
|
|
|
|
nsAutoString title;
|
|
nsresult rv = aAlert->GetTitle(title);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
notification.title = nsCocoaUtils::ToNSString(title);
|
|
|
|
nsAutoString hostPort;
|
|
rv = aAlert->GetSource(hostPort);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsCOMPtr<nsIStringBundle> bundle;
|
|
nsCOMPtr<nsIStringBundleService> sbs =
|
|
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
|
|
sbs->CreateBundle("chrome://alerts/locale/alert.properties",
|
|
getter_AddRefs(bundle));
|
|
|
|
if (!hostPort.IsEmpty() && bundle) {
|
|
AutoTArray<nsString, 1> formatStrings = {hostPort};
|
|
nsAutoString notificationSource;
|
|
bundle->FormatStringFromName("source.label", formatStrings,
|
|
notificationSource);
|
|
notification.subtitle = nsCocoaUtils::ToNSString(notificationSource);
|
|
}
|
|
|
|
nsAutoString text;
|
|
rv = aAlert->GetText(text);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
notification.informativeText = nsCocoaUtils::ToNSString(text);
|
|
|
|
bool isSilent;
|
|
aAlert->GetSilent(&isSilent);
|
|
notification.soundName = isSilent ? nil : NSUserNotificationDefaultSoundName;
|
|
|
|
NSMutableArray* additionalActions = [[NSMutableArray alloc] init];
|
|
|
|
nsTArray<RefPtr<nsIAlertAction>> actions;
|
|
MOZ_TRY(aAlert->GetActions(actions));
|
|
|
|
for (const RefPtr<nsIAlertAction>& action : actions) {
|
|
nsAutoString actionName;
|
|
MOZ_TRY(action->GetAction(actionName));
|
|
|
|
nsAutoString actionTitle;
|
|
MOZ_TRY(action->GetTitle(actionTitle));
|
|
|
|
// Add suffix to prevent potential collision with keywords like "settings"
|
|
NSString* actionNameNS =
|
|
nsCocoaUtils::ToNSString(actionName + kActionSuffix);
|
|
NSString* actionTitleNS = nsCocoaUtils::ToNSString(actionTitle);
|
|
NSUserNotificationAction* notificationAction =
|
|
[NSUserNotificationAction actionWithIdentifier:actionNameNS
|
|
title:actionTitleNS];
|
|
[additionalActions addObject:notificationAction];
|
|
}
|
|
|
|
// If this is not an application/extension alert, show additional actions
|
|
// dealing with permissions.
|
|
bool isActionable;
|
|
if (bundle && NS_SUCCEEDED(aAlert->GetActionable(&isActionable)) &&
|
|
isActionable) {
|
|
nsAutoString disableButtonTitle;
|
|
if (!hostPort.IsEmpty()) {
|
|
AutoTArray<nsString, 1> formatStrings = {hostPort};
|
|
bundle->FormatStringFromName("webActions.disableForOrigin.label",
|
|
formatStrings, disableButtonTitle);
|
|
}
|
|
|
|
nsAutoString settingsButtonTitle;
|
|
bundle->GetStringFromName("webActions.settings.label", settingsButtonTitle);
|
|
|
|
NSString* actionNameNS = nsCocoaUtils::ToNSString(kAlertActionDisable);
|
|
NSString* actionTitleNS = nsCocoaUtils::ToNSString(disableButtonTitle);
|
|
NSUserNotificationAction* notificationAction =
|
|
[NSUserNotificationAction actionWithIdentifier:actionNameNS
|
|
title:actionTitleNS];
|
|
[additionalActions addObject:notificationAction];
|
|
|
|
actionNameNS = nsCocoaUtils::ToNSString(kAlertActionSettings);
|
|
actionTitleNS = nsCocoaUtils::ToNSString(settingsButtonTitle);
|
|
notificationAction =
|
|
[NSUserNotificationAction actionWithIdentifier:actionNameNS
|
|
title:actionTitleNS];
|
|
[additionalActions addObject:notificationAction];
|
|
}
|
|
|
|
notification.additionalActions = additionalActions;
|
|
notification.hasActionButton = additionalActions.count == 0;
|
|
[additionalActions release];
|
|
|
|
nsAutoString name;
|
|
rv = aAlert->GetName(name);
|
|
// Don't let an alert name be more than MAX_NOTIFICATION_NAME_LEN characters.
|
|
// More than that shouldn't be necessary and userInfo (assigned to below) has
|
|
// a length limit of 16k on MacOS 10.11. Exception thrown if limit exceeded.
|
|
if (name.Length() > MAX_NOTIFICATION_NAME_LEN) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
NSString* alertName = nsCocoaUtils::ToNSString(name);
|
|
if (!alertName) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
notification.userInfo = [NSDictionary
|
|
dictionaryWithObjects:[NSArray arrayWithObjects:alertName, nil]
|
|
forKeys:[NSArray arrayWithObjects:@"name", nil]];
|
|
|
|
nsAutoString cookie;
|
|
rv = aAlert->GetCookie(cookie);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<imgIContainer> image;
|
|
MOZ_TRY(aAlert->GetImage(getter_AddRefs(image)));
|
|
if (image) {
|
|
NSImage* cocoaImage = nil;
|
|
// TODO: Pass pres context / ComputedStyle here to support context paint
|
|
// properties.
|
|
// TODO: Do we have a reasonable size to pass around here?
|
|
nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer(
|
|
image, imgIContainer::FRAME_FIRST, nullptr, NSMakeSize(0, 0),
|
|
&cocoaImage);
|
|
notification.contentImage = cocoaImage;
|
|
[cocoaImage release];
|
|
}
|
|
|
|
bool inPrivateBrowsing;
|
|
rv = aAlert->GetInPrivateBrowsing(&inPrivateBrowsing);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
MacOSNotificationInfo* macosni = new MacOSNotificationInfo(
|
|
alertName, aAlert, aAlertListener, cookie, inPrivateBrowsing);
|
|
|
|
CloseAlertCocoaString(alertName);
|
|
mActiveAlerts.AppendElement(macosni);
|
|
[GetNotificationCenter() deliverNotification:notification];
|
|
if (aAlertListener) {
|
|
aAlertListener->Observe(nullptr, "alertshow", cookie.get());
|
|
}
|
|
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::CloseAlert(const nsAString& aAlertName,
|
|
bool aContextClosed) {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
|
|
|
|
NSString* alertName = nsCocoaUtils::ToNSString(aAlertName);
|
|
CloseAlertCocoaString(alertName);
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
NS_IMETHODIMP MacOSNotificationCenter::Teardown() {
|
|
mActiveAlerts.Clear();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP MacOSNotificationCenter::PbmTeardown() {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
|
|
|
|
NSArray* notifications = [GetNotificationCenter() deliveredNotifications];
|
|
for (int32_t i = mActiveAlerts.Length() - 1; i >= 0; i--) {
|
|
MacOSNotificationInfo* macosni = mActiveAlerts[i];
|
|
if (!macosni->mPrivateBrowsing) {
|
|
continue;
|
|
}
|
|
NSString* name = macosni->mName;
|
|
for (NSUserNotification* notification in notifications) {
|
|
NSString* notifName = [[notification userInfo] valueForKey:@"name"];
|
|
if ([notifName isEqualToString:name]) {
|
|
[GetNotificationCenter() removeDeliveredNotification:notification];
|
|
break;
|
|
}
|
|
}
|
|
if (macosni->mObserver) {
|
|
macosni->mObserver->Observe(nullptr, "alertfinished",
|
|
macosni->mCookie.get());
|
|
}
|
|
mActiveAlerts.RemoveElementAt(i);
|
|
}
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
void MacOSNotificationCenter::CloseAlertCocoaString(NSString* aAlertName) {
|
|
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
|
|
|
if (!aAlertName) {
|
|
return; // Can't do anything without a name
|
|
}
|
|
|
|
NSArray* notifications = [GetNotificationCenter() deliveredNotifications];
|
|
for (NSUserNotification* notification in notifications) {
|
|
NSString* name = [[notification userInfo] valueForKey:@"name"];
|
|
if ([name isEqualToString:aAlertName]) {
|
|
[GetNotificationCenter() removeDeliveredNotification:notification];
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
|
|
MacOSNotificationInfo* macosni = mActiveAlerts[i];
|
|
if ([aAlertName isEqualToString:macosni->mName]) {
|
|
if (macosni->mObserver) {
|
|
macosni->mObserver->Observe(nullptr, "alertfinished",
|
|
macosni->mCookie.get());
|
|
}
|
|
mActiveAlerts.RemoveElementAt(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
|
}
|
|
|
|
void MacOSNotificationCenter::OnActivate(
|
|
NSString* aAlertName, NSUserNotificationActivationType aActivationType,
|
|
NSUserNotificationAction* aAdditionalActivationAction) {
|
|
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
|
|
|
|
if (!aAlertName) {
|
|
return; // Can't do anything without a name
|
|
}
|
|
|
|
for (unsigned int i = 0; i < mActiveAlerts.Length(); i++) {
|
|
MacOSNotificationInfo* macosni = mActiveAlerts[i];
|
|
if ([aAlertName isEqualToString:macosni->mName]) {
|
|
if (macosni->mObserver) {
|
|
switch ((int)aActivationType) {
|
|
case NSUserNotificationActivationTypeAdditionalActionClicked: {
|
|
MOZ_ASSERT(aAdditionalActivationAction);
|
|
nsAutoString actionName;
|
|
nsCocoaUtils::GetStringForNSString(
|
|
aAdditionalActivationAction.identifier, actionName);
|
|
|
|
if (actionName == kAlertActionDisable) {
|
|
macosni->mObserver->Observe(nullptr, "alertdisablecallback",
|
|
macosni->mCookie.get());
|
|
break;
|
|
}
|
|
if (actionName == kAlertActionSettings) {
|
|
macosni->mObserver->Observe(nullptr, "alertsettingscallback",
|
|
macosni->mCookie.get());
|
|
break;
|
|
}
|
|
|
|
// Trim the suffix
|
|
actionName.Truncate(actionName.Length() - kActionSuffix.Length());
|
|
|
|
nsCOMPtr<nsIAlertAction> action;
|
|
macosni->mAlertNotification->GetAction(actionName,
|
|
getter_AddRefs(action));
|
|
macosni->mObserver->Observe(action, "alertclickcallback",
|
|
macosni->mCookie.get());
|
|
break;
|
|
}
|
|
case NSUserNotificationActivationTypeActionButtonClicked:
|
|
default:
|
|
macosni->mObserver->Observe(nullptr, "alertclickcallback",
|
|
macosni->mCookie.get());
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
NS_OBJC_END_TRY_IGNORE_BLOCK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::GetHistory(nsTArray<nsString>& aResult) {
|
|
// NSUserNotificationCenter doesn't support this, blocked by the migration to
|
|
// UNUserNotificationCenter which has
|
|
// getDeliveredNotificationsWithCompletionHandler
|
|
// https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/getdeliverednotifications(completionhandler:)?language=objc
|
|
// See bug 1971395.
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::IsFullscreen(bool* aRetVal) {
|
|
*aRetVal = false;
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// nsIAlertsDoNotDisturb
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::GetManualDoNotDisturb(bool* aRetVal) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::SetManualDoNotDisturb(bool aDoNotDisturb) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::GetSuppressForScreenSharing(bool* aRetVal) {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN
|
|
|
|
NS_ENSURE_ARG(aRetVal);
|
|
*aRetVal = mSuppressForScreenSharing;
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
MacOSNotificationCenter::SetSuppressForScreenSharing(bool aSuppress) {
|
|
NS_OBJC_BEGIN_TRY_BLOCK_RETURN
|
|
|
|
mSuppressForScreenSharing = aSuppress;
|
|
return NS_OK;
|
|
|
|
NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE)
|
|
}
|
|
|
|
} // namespace mozilla
|