mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-22 04:32:22 +02:00
93 lines
3.7 KiB
Plaintext
93 lines
3.7 KiB
Plaintext
// Neutralizes a Chromium browser-process crash: releasing the mouse OUTSIDE the
|
|
// window during a drag (trivial with a second display) makes RootView::UpdateCursor
|
|
// deref a null view (GetEventHandlerForPoint returns null for an off-widget point,
|
|
// root_view.cc:852) and SIGSEGVs the whole app. We can't catch it in JS, it's
|
|
// upstream of our renderer, so we sit on a supported AppKit local event monitor and
|
|
// snap any off-window mouse-UP to the window edge before Chromium hit-tests it; the
|
|
// lookup then always finds a view, so the null deref is unreachable. macOS-only.
|
|
//
|
|
// Fail-open in both directions: anything unexpected falls through to the original
|
|
// event, and we only touch releases that land fully outside the window, so the
|
|
// worst case is "behaves exactly like today".
|
|
|
|
#include <node_api.h>
|
|
#import <Cocoa/Cocoa.h>
|
|
#include "clamp_decision.h"
|
|
|
|
static id gMonitor = nil;
|
|
|
|
static NSEvent *ClampOffWindowRelease(NSEvent *event) {
|
|
@try {
|
|
NSEventType t = [event type];
|
|
if (t != NSEventTypeLeftMouseUp && t != NSEventTypeRightMouseUp &&
|
|
t != NSEventTypeOtherMouseUp) {
|
|
return event;
|
|
}
|
|
NSWindow *win = [event window];
|
|
NSPoint p;
|
|
if (win && [win contentView]) {
|
|
// Normal case: the captured window rode along on the event.
|
|
p = [event locationInWindow];
|
|
} else {
|
|
// The original gap: a release off the source window (easy with a second
|
|
// display) can arrive with no window attached, so the old code fail-opened
|
|
// here and the crash slipped through. Fall back to the key/main window and
|
|
// map the screen-space location into it so we can still snap it.
|
|
win = [NSApp keyWindow] ?: [NSApp mainWindow];
|
|
if (!win || ![win contentView]) return event;
|
|
p = [win convertPointFromScreen:[event locationInWindow]];
|
|
}
|
|
NSView *content = [win contentView];
|
|
NSSize ws = [win frame].size;
|
|
NSRect cb = [content frame];
|
|
// all the misfire-prone arithmetic lives in clamp_decision() so the property
|
|
// test exercises the exact code that ships
|
|
ClampDecision d = clamp_decision(p.x, p.y, ws.width, ws.height,
|
|
cb.origin.x, cb.origin.y,
|
|
cb.size.width, cb.size.height);
|
|
if (!d.clamp) return event;
|
|
NSEvent *clamped =
|
|
[NSEvent mouseEventWithType:t
|
|
location:NSMakePoint(d.x, d.y)
|
|
modifierFlags:[event modifierFlags]
|
|
timestamp:[event timestamp]
|
|
windowNumber:[win windowNumber]
|
|
context:nil
|
|
eventNumber:[event eventNumber]
|
|
clickCount:[event clickCount]
|
|
pressure:[event pressure]];
|
|
return clamped ? clamped : event;
|
|
} @catch (...) {
|
|
return event;
|
|
}
|
|
}
|
|
|
|
static napi_value Install(napi_env env, napi_callback_info info) {
|
|
bool ok = false;
|
|
@autoreleasepool {
|
|
if (gMonitor == nil) {
|
|
NSEventMask mask = NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp |
|
|
NSEventMaskOtherMouseUp;
|
|
gMonitor = [[NSEvent addLocalMonitorForEventsMatchingMask:mask
|
|
handler:^NSEvent *(NSEvent *e) {
|
|
return ClampOffWindowRelease(e);
|
|
}] retain];
|
|
ok = (gMonitor != nil);
|
|
} else {
|
|
ok = true;
|
|
}
|
|
}
|
|
napi_value result;
|
|
napi_get_boolean(env, ok, &result);
|
|
return result;
|
|
}
|
|
|
|
static napi_value Init(napi_env env, napi_value exports) {
|
|
napi_value fn;
|
|
napi_create_function(env, "install", NAPI_AUTO_LENGTH, Install, NULL, &fn);
|
|
napi_set_named_property(env, exports, "install", fn);
|
|
return exports;
|
|
}
|
|
|
|
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|