Conversation
TrackPopupMenu without TPM_RETURNCMD posts WM_COMMAND, so the click was emitted after Open() returned, from the host message loop. Bindings whose callbacks are only valid inside a call (Dart NativeCallable.isolateLocal) abort there: 'Cannot invoke native callback outside an isolate'. Use TPM_RETURNCMD and emit the chosen item's click (searching submenus) before returning, as the WinUI 3 backend already does. A genuine TrackPopupMenu failure still returns false. menu_backend_test: native-backend check that the click listener ran before Open() returned.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #52.
The problem
On the native Windows backend,
Menu::Open()callsTrackPopupMenuwithoutTPM_RETURNCMD. Windowstherefore posts
WM_COMMANDfor the chosen item, andOpen()returns before that message is handled. Theclick is emitted later, when the host window's message loop delivers it.
A binding whose callbacks are only valid while its caller is inside the call cannot receive an event that
late. In nativeapi-flutter 0.2.4 the Dart listener is a
NativeCallable.isolateLocal, and the Dart VMaborts the process when it is invoked with no isolate on the thread. I saw this on Flutter 3.41.2 and
Windows 11, in a release build with the merged platform and UI thread the example README asks for. Every
click on a menu item killed the app:
The opened and closed events were always safe, because
WM_INITMENUPOPUPandWM_UNINITMENUPOPUParrivewhile
Open()is still on the stack. Only the click was late.The change
Menu::Open()now passesTPM_RETURNCMD, looks up the returned command id among its items and theirsubmenus, and fires that item's click before it returns. The WinUI 3 backend already emits clicks inside
Open(), so the two backends now behave alike.A return of zero under
TPM_RETURNCMDmeans either that the user dismissed the menu or that the callfailed, so the code clears the error state beforehand and returns
falseonly for a real failure.TPM_NONOTIFYis deliberately absent, since it suppressesWM_INITMENUPOPUPandWM_UNINITMENUPOPUPas well, and I lost the opened and closed events when I tried it.
tests/menu_backend_test.cppgains a check in the interactive section. It opens a native-backend menu,sends Down and Enter from a timer, and asserts that the listener ran before
Open()returned.Verification
The new check fails on
mainwith "Native click was not emitted before Open() returned" and passes withthis change, built with Visual Studio 18 BuildTools and CMake on Windows 11.
I also vendored this patch into cnativeapi 0.2.4 and drove a small Flutter app with it. It ran six open
and pick rounds and six open and dismiss rounds, over a menu rebuilt on every open and a menu built once
and reused. Every round logged its opened event, its closed event and, where an item was picked, its
click, all before
open()returned.One thing this change does not address, in case it matters to you. Destroying a
MenuorMenuItemaborts the process, whether Dart's collector frees it or the program calls
dispose()itself. In my appa menu rebuilt on every open died on the second pick, four runs out of four, while a menu built once and
kept alive survived every run. It is unrelated to the click path above, and I am happy to open a separate
issue with the traces if that would help.