-
-
Notifications
You must be signed in to change notification settings - Fork 112
Description
Bug
ApplicationMenu.setApplicationMenu() silently fails to create any menu. The native menu bar only shows the app name with no custom menus, and keyboard accelerators never fire.
Root Cause
toCString() in dist/api/bun/proc/native.ts creates a Buffer and returns a raw pointer via ptr(buf). The Buffer itself is not stored anywhere — it's a local temporary eligible for GC immediately after the FFI call returns.
The native setApplicationMenu in nativeWrapper.mm does this:
extern "C" void setApplicationMenu(const char *jsonString, ...) {
NSLog(@"Setting application menu from JSON in objc");
dispatch_async(dispatch_get_main_queue(), ^{
NSData *jsonData = [NSData dataWithBytes:jsonString length:strlen(jsonString)];
// ^ jsonString pointer is already dangling here
});
}The dispatch_async block captures jsonString by pointer, not by value. By the time the block executes on the main thread, Bun's GC has already freed the Buffer backing the pointer. strlen() on freed memory returns 0, NSData gets 0 bytes, and NSJSONSerialization fails with:
Failed to parse JSON: Error Domain=NSCocoaErrorDomain Code=3840 "Unable to parse empty data."
The menu is silently never created. No crash, no other error.
Diagnosis Trail
The only visible symptoms are:
- Menu bar shows only the app name (no custom menus)
- This log line appears ~20ms after "Setting application menu from JSON in objc":
Failed to parse JSON: Error Domain=NSCocoaErrorDomain Code=3840 "Unable to parse empty data." - The
application-menu-clickedevent never fires for any action or accelerator
Fix
Keep a reference to the Buffer alive until the main thread has consumed it. Minimal JS-side fix in dist/api/bun/proc/native.ts:
setApplicationMenu: (params: { menuConfig: string }): void => {
const { menuConfig } = params;
+ // Prevent GC from freeing the buffer before dispatch_async runs on main thread
+ const buf = Buffer.from(menuConfig + "\0", "utf8");
+ (globalThis as any).__electrobun_menuConfigBuf = buf;
native.symbols.setApplicationMenu(
- toCString(menuConfig),
+ ptr(buf),
applicationMenuHandler,
);
},The proper native-side fix would be to copy the JSON string into an NSString before dispatch_async:
extern "C" void setApplicationMenu(const char *jsonString, ZigStatusItemHandler zigTrayItemHandler) {
NSLog(@"Setting application menu from JSON in objc");
NSString *jsonCopy = [NSString stringWithUTF8String:jsonString]; // copy while pointer is valid
dispatch_async(dispatch_get_main_queue(), ^{
NSData *jsonData = [jsonCopy dataUsingEncoding:NSUTF8StringEncoding];
// ... rest unchanged
});
}The same pattern likely affects showContextMenu and setTrayMenuFromJSON but may be less reproducible due to timing differences.
Environment
- electrobun: 1.12.3
- macOS: 15.5 arm64
- Bun: (via electrobun bundled runtime)