commit 4a916f94b05deff8b34f00bcbefb2cf02bb72b2d
parent 69e6e8511d0bc0aefbdfa1573b284502ac87b1b9
Author: Stephen A Pohl <spohl@mozilla.com>
Date: Tue, 9 Dec 2025 00:52:19 +0000
Bug 1947539: Ensure that not more than one app modal dialog can be opened on macOS. r=mac-reviewers,bradwerth
macOS itself does not guarantee that only one app modal dialog is opened when the various NS*Panel's `runModal` methods are executed. This patch ensures that these panels are truly app modal to prevent various undesirable bugs, such as browser windows not responding to clicks anymore.
Differential Revision: https://phabricator.services.mozilla.com/D275526
Diffstat:
4 files changed, 44 insertions(+), 10 deletions(-)
diff --git a/widget/cocoa/nsCocoaUtils.h b/widget/cocoa/nsCocoaUtils.h
@@ -250,7 +250,12 @@ class nsCocoaUtils {
*/
static BOOL ShouldRestoreStateDueToLaunchAtLogin();
- static void PrepareForNativeAppModalDialog();
+ /**
+ * Returns true if the application is ready to run an app modal dialog, false
+ * otherwise. This has to be balanced with a call to
+ * CleanUpAfterNativeAppModalDialog once the app modal dialog is closed.
+ */
+ static bool PrepareForNativeAppModalDialog();
static void CleanUpAfterNativeAppModalDialog();
// 3 utility functions to go from a frame of imgIContainer to CGImage and then
diff --git a/widget/cocoa/nsCocoaUtils.mm b/widget/cocoa/nsCocoaUtils.mm
@@ -324,21 +324,32 @@ BOOL nsCocoaUtils::ShouldRestoreStateDueToLaunchAtLogin() {
return NO;
}
-void nsCocoaUtils::PrepareForNativeAppModalDialog() {
- NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+static bool sIsActivelyShowingAppModalDialog = false;
+
+bool nsCocoaUtils::PrepareForNativeAppModalDialog() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sIsActivelyShowingAppModalDialog) {
+ return false;
+ }
+ sIsActivelyShowingAppModalDialog = true;
if (!NSApp.active) {
// Early exit if the app isn't active. This is because we can't safely
// set the NSApp.mainMenu property in such a case. We early exit so we
// also don't invoke any side effects.
- return;
+ return true;
}
// Don't do anything if this is embedding. We'll assume that if there is no
// hidden window we shouldn't do anything, and that should cover the embedding
// case.
nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
- if (!hiddenWindowMenuBar) return;
+ if (!hiddenWindowMenuBar) {
+ return true;
+ }
// First put up the hidden window menu bar so that app menu event handling is
// correct.
@@ -366,12 +377,20 @@ void nsCocoaUtils::PrepareForNativeAppModalDialog() {
[NSApp setMainMenu:newMenuBar];
[newMenuBar release];
- NS_OBJC_END_TRY_IGNORE_BLOCK;
+ return true;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(sIsActivelyShowingAppModalDialog = false);
}
void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() {
NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sIsActivelyShowingAppModalDialog) {
+ return;
+ }
+
// Don't do anything if this is embedding. We'll assume that if there is no
// hidden window we shouldn't do anything, and that should cover the embedding
// case.
@@ -387,6 +406,8 @@ void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() {
[WindowDelegate paintMenubarForWindow:mainWindow];
}
+ sIsActivelyShowingAppModalDialog = false;
+
NS_OBJC_END_TRY_IGNORE_BLOCK;
}
diff --git a/widget/cocoa/nsFilePicker.mm b/widget/cocoa/nsFilePicker.mm
@@ -375,7 +375,9 @@ nsIFilePicker::ResultCode nsFilePicker::GetLocalFiles(
}
int result;
- nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (!nsCocoaUtils::PrepareForNativeAppModalDialog()) {
+ return retVal;
+ }
if (mFilters.Length() > 1) {
// [NSURL initWithString:] (below) throws an exception if URLString is nil.
@@ -469,7 +471,9 @@ nsIFilePicker::ResultCode nsFilePicker::GetLocalFolder(nsIFile** outFile) {
if (theDir) {
[thePanel setDirectoryURL:[NSURL fileURLWithPath:theDir isDirectory:YES]];
}
- nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (!nsCocoaUtils::PrepareForNativeAppModalDialog()) {
+ return retVal;
+ }
int result = [thePanel runModal];
nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
@@ -554,7 +558,9 @@ nsIFilePicker::ResultCode nsFilePicker::PutLocalFile(nsIFile** outFile) {
}
// load the panel
- nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (!nsCocoaUtils::PrepareForNativeAppModalDialog()) {
+ return retVal;
+ }
[thePanel setNameFieldStringValue:defaultFilename];
int result = [thePanel runModal];
nsCocoaUtils::CleanUpAfterNativeAppModalDialog();
diff --git a/widget/cocoa/nsPrintDialogX.mm b/widget/cocoa/nsPrintDialogX.mm
@@ -134,7 +134,9 @@ nsPrintDialogServiceX::ShowPrintDialog(mozIDOMWindowProxy* aParent,
[viewController release];
// Show the dialog.
- nsCocoaUtils::PrepareForNativeAppModalDialog();
+ if (!nsCocoaUtils::PrepareForNativeAppModalDialog()) {
+ return NS_ERROR_FAILURE;
+ }
int button = [panel runModal];
nsCocoaUtils::CleanUpAfterNativeAppModalDialog();