tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mwidget/cocoa/nsCocoaUtils.h | 7++++++-
Mwidget/cocoa/nsCocoaUtils.mm | 31++++++++++++++++++++++++++-----
Mwidget/cocoa/nsFilePicker.mm | 12+++++++++---
Mwidget/cocoa/nsPrintDialogX.mm | 4+++-
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();