Bug 1707598 - Add -[MOZMenuOpeningCoordinator runAfterMenuClosed:]. r=harry

This is a robust way to queue runnables which won't run until after the nested
event loop of the open NSMenu has been exited.
NS_DispatchToCurrentThread is not a reliable way to achieve that, because it
puts the runnables into the Gecko event loop, and there's nothing that prevents
us from potentially visiting the Gecko event loop inside the NSMenu's event loop,
even after cancelTracking(WithoutAnimation) has been called.

Differential Revision: https://phabricator.services.mozilla.com/D113733
This commit is contained in:
Markus Stange 2021-04-30 17:45:58 +00:00
parent 83880d0070
commit 85bc33f0da
2 changed files with 32 additions and 0 deletions

View File

@ -8,6 +8,12 @@
#import <Cocoa/Cocoa.h>
#include "mozilla/RefPtr.h"
namespace mozilla {
class Runnable;
}
/*
* MOZMenuOpeningCoordinator is a workaround for the fact that opening an NSMenu creates a nested
* event loop. This event loop is only exited after the menu is closed. The caller of
@ -32,6 +38,10 @@
// Can only be called on the main thread.
- (void)cancelAsynchronousOpening:(NSInteger)aHandle;
// Run aRunnable once the nested event loop of the currently open menu has been exited.
// If no menu is currently open, post the runnable with NS_DispatchToCurrentThread.
- (void)runAfterMenuClosed:(RefPtr<mozilla::Runnable>&&)aRunnable;
@end
#endif // MOZMenuOpeningCoordinator_h

View File

@ -14,7 +14,9 @@
#include "nsCocoaFeatures.h"
#include "nsCocoaUtils.h"
#include "nsDeque.h"
#include "nsObjCExceptions.h"
#include "nsThreadUtils.h"
#include "SDKDeclarations.h"
@interface MOZMenuOpeningInfo : NSObject
@ -32,6 +34,10 @@
// time at at which it is unqueued in _runMenu.
MOZMenuOpeningInfo* mPendingOpening; // strong
// Any runnables we want to run after the current menu event loop has been exited.
// Only non-empty if mRunMenuIsOnTheStack is true.
nsRefPtrDeque<mozilla::Runnable> mPendingAfterMenuCloseRunnables;
// An incrementing counter
NSInteger mLastHandle;
@ -99,6 +105,12 @@
}
[info release];
// We have exited _openMenu's nested event loop. Dispatch any pending "after menu close"
// runnables to the event loop.
while (mPendingAfterMenuCloseRunnables.GetSize() != 0) {
NS_DispatchToCurrentThread(mPendingAfterMenuCloseRunnables.PopFront());
}
}
mRunMenuIsOnTheStack = NO;
@ -111,6 +123,16 @@
}
}
- (void)runAfterMenuClosed:(RefPtr<mozilla::Runnable>&&)aRunnable {
MOZ_RELEASE_ASSERT(aRunnable);
if (mRunMenuIsOnTheStack) {
mPendingAfterMenuCloseRunnables.Push(aRunnable.forget());
} else {
NS_DispatchToCurrentThread(aRunnable.forget());
}
}
- (void)_openMenu:(NSMenu*)aMenu atScreenPosition:(NSPoint)aPosition forView:(NSView*)aView {
// There are multiple ways to display an NSMenu as a context menu.
//